packages feed

large-hashable-0.1.1.0: src/Data/LargeHashable/TH.hs

{-# LANGUAGE CPP             #-}
{-# LANGUAGE QuasiQuotes     #-}
{-# LANGUAGE TemplateHaskell #-}
module Data.LargeHashable.TH (

    deriveLargeHashable, deriveLargeHashableCtx, deriveLargeHashableNoCtx
  , deriveLargeHashableCustomCtx

) where

import Control.Arrow (first)
import Control.Monad (forM)
import Data.LargeHashable.Class
import Data.Word
import Language.Haskell.TH

-- | Template Haskell function to automatically derive
--   instances of 'LargeHashable'. The derived instances first
--   calls 'updateHash' with an unique identifier number for
--   every constructor, followed by 'updateHash' calls for every
--   field of the constructor (if existent). It also works for
--   type families.
--
--   E. g. for the following code
--
--   @
--
-- data BlaFoo a = Foo
--               | Bar Int a
--               | Baz a a
--
-- $(deriveLargeHashable ''BlaFoo)
--   @
--
--   The following instance gets generated:
--
--   @
-- instance LargeHashable a_apg8 =>
--         LargeHashable (BlaFoo a_apg8) where
--  updateHash Foo = updateHash (0 :: Foreign.C.Types.CULong)
--  updateHash (Bar a b)
--    = (((updateHash (1 :: Foreign.C.Types.CULong)) >> (updateHash a))
--       >> (updateHash b))
--  updateHash (XY a b)
--    = (((updateHash (2 :: Foreign.C.Types.CULong)) >> (updateHash a))
--       >> (updateHash b))
--    @
deriveLargeHashable :: Name -> Q [Dec]
deriveLargeHashable n = reify n >>= \info ->
    case info of
        TyConI dec ->
            case dec of
#if MIN_VERSION_template_haskell(2,11,0)
                DataD context name tyvars _ cons _ ->
#else
                DataD context name tyvars cons _ ->
#endif
                    buildInstance (ConT name) context tyvars cons

#if MIN_VERSION_template_haskell(2,11,0)
                NewtypeD context name tyvars _ con _  ->
#else
                NewtypeD context name tyvars con _  ->
#endif
                    buildInstance (ConT name) context tyvars [con]
                _ -> fail $ notDeriveAbleErrorMsg n info
        FamilyI _ instDecs -> fmap concat $ forM instDecs $ \instDec ->
            case instDec of
#if MIN_VERSION_template_haskell(2,15,0)
                DataInstD context _ ty _ cons _ ->
#elif MIN_VERSION_template_haskell(2,11,0)
                DataInstD context name types _ cons _ -> let ty = foldl AppT (ConT name) types in
#else
                DataInstD context name types cons _ -> let ty = foldl AppT (ConT name) types in
#endif
                    buildInstance ty context [] cons
#if MIN_VERSION_template_haskell(2,15,0)
                NewtypeInstD context _ ty _ con _ ->
#elif MIN_VERSION_template_haskell(2,11,0)
                NewtypeInstD context name types _ con _ -> let ty = foldl AppT (ConT name) types in
#else
                NewtypeInstD context name types con _ -> let ty = foldl AppT (ConT name) types in
#endif
                    buildInstance ty context [] [con]
                _ -> fail $ notDeriveAbleErrorMsg n info
        _ -> fail $ notDeriveAbleErrorMsg n info

-- | Derive a 'LargeHashable' instance with extra constraints in the
-- context of the instance.
deriveLargeHashableCtx ::
       Name
    -> ([TypeQ] -> [PredQ])
       -- ^ Function mapping the type variables in the instance head to the additional constraints
    -> Q [Dec]
deriveLargeHashableCtx tyName extraPreds =
    deriveLargeHashableCustomCtx tyName mkCtx
    where
      mkCtx args oldCtx =
          oldCtx ++ extraPreds args

-- | Derive a 'LargeHashable' instance with no constraints in the context of the instance.
deriveLargeHashableNoCtx ::
       Name
    -> (Q [Dec])
deriveLargeHashableNoCtx tyName =
    deriveLargeHashableCustomCtx tyName (\_ _ -> [])

-- | Derive a 'LargeHashable' instance with a completely custom instance context.
deriveLargeHashableCustomCtx ::
       Name
    -> ([TypeQ] -> [PredQ] -> [PredQ])
       -- ^ Function mapping the type variables in the instance head and the
       -- constraints that would normally be generated to the constraints
       -- that should be generated.
    -> (Q [Dec])
deriveLargeHashableCustomCtx tyName extraPreds =
    do decs <- deriveLargeHashable tyName
       case decs of
#if MIN_VERSION_template_haskell(2,11,0)
         (InstanceD overlap ctx ty body : _) ->
#else
         (InstanceD ctx ty body : _) ->
#endif
             do let args = reverse (collectArgs ty)
                newCtx <- sequence (extraPreds (map return args) (map return ctx))
                -- _ <- fail ("args: " ++ show args ++", ty: " ++ show ty)
#if MIN_VERSION_template_haskell(2,11,0)
                return [InstanceD overlap newCtx ty body]
#else
                return [InstanceD newCtx ty body]
#endif
         _ ->
             error $
                 "Unexpected declarations returned by deriveLargeHashable: " ++ show (ppr decs)
    where
      collectArgs :: Type -> [Type]
      collectArgs outerTy =
          let loop ty =
                  case ty of
                    (AppT l r) ->
                        case l of
                          AppT _ _ -> r : loop l
                          _ -> [r]
                    _ -> []
          in case outerTy of
               AppT _ r -> loop r
               _ -> []

-- | Generates the error message displayed when somebody tries to let us
--   derive impossible instances!
notDeriveAbleErrorMsg :: Name -> Info -> String
notDeriveAbleErrorMsg name info = "Could not derive LargeHashable instance for "
    ++ (show name) ++ "(" ++ (show info) ++ "). If you think this should be possible, file an issue."

-- | After 'deriveLargeHashable' has matched all the important information
--   this function gets called to build the instance declaration.
#if MIN_VERSION_template_haskell(2,17,0)
buildInstance :: Type -> Cxt -> [TyVarBndr f] -> [Con] ->  Q [Dec]
#else
buildInstance :: Type -> Cxt -> [TyVarBndr] -> [Con] ->  Q [Dec]
#endif
buildInstance basicType context vars cons =
    let consWithIds = zip [0..] cons
        constraints = makeConstraints context vars
        typeWithVars = foldl appT (return basicType) $ map (varT . varName) vars
      in (:[]) <$> instanceD constraints (conT ''LargeHashable `appT` typeWithVars)
            [updateHashDeclaration 'updateHash consWithIds,
             updateHashDeclaration 'updateHashStable consWithIds]

-- | This function generates the declaration for the 'updateHash' and the
--   'updateHashStable functions
--   of the 'LargeHashable' typeclass. By taking the constructors with there
--   selected IDs and calling 'updateHashClause' for everyone of them to generate
--   the corresponding clause.
updateHashDeclaration :: Name -> [(Integer, Con)] -> Q Dec
updateHashDeclaration name [(_, con)] =
    funD name [updateHashClause name Nothing con]
updateHashDeclaration name consWIds =
    funD name (map (uncurry (updateHashClause name) . first Just) consWIds)

-- | 'updateHashClause' generates a clause of the 'updateHash' function.
--   It makes sure all the fields are matched correctly and updates the hash
--   with the neccessary information about the constructor (its ID) and all
--   of its fields.
updateHashClause :: Name -> Maybe Integer -> Con -> Q Clause
updateHashClause name mI con =
        clause [return patOfClause]
            (normalB $
                foldl sequenceExps
                    conMarker
                    hashUpdatesOfConFields)
            []
    where conMarker = case mI of
                        Just i -> [| updateHash ($(litE . IntegerL $ i) :: Word64) |]
                        Nothing -> [| return () |]
          hashUpdatesOfConFields = map (\pn -> [| $(varE name) $(varE pn) |]) patVarNames
          -- Extract the names of all the
          -- pattern variables from usedPat.
          patVarNames = case patOfClause of
#if MIN_VERSION_template_haskell(2,18,0)
                          ConP _ _ vars -> map (\(VarP v) -> v) vars
#else
                          ConP _ vars -> map (\(VarP v) -> v) vars
#endif
                          InfixP (VarP v1) _ (VarP v2) -> [v1, v2]
                          _ -> error "Pattern in patVarNames not matched!"
          patOfClause = patternForCon con

-- | Generate a Pattern that matches the supplied constructor
--   and all of its fields.
patternForCon :: Con -> Pat
patternForCon con = case con of
              NormalC n types -> conP n $ uniqueVarPats (length types)
              RecC n varTypes -> conP n $ uniqueVarPats (length varTypes)
              InfixC _ n _ -> InfixP (VarP . mkName $ "x") n (VarP . mkName $ "y")
              c@(ForallC{}) -> error $ "Cannot derive quantified type as it would potentially violate uniqueness: "++ show c
#if MIN_VERSION_template_haskell(2,11,0)
              GadtC [n] types _ -> conP n $ uniqueVarPats (length types)
              RecGadtC [n] varTypes _ -> conP n $ uniqueVarPats (length varTypes)
              _ -> error $ "Constructor not supported: "++show con
#endif
    where
        uniqueVarPats n = take n . map (VarP . mkName) $ names
        conP n =
#if MIN_VERSION_template_haskell(2,18,0)
            ConP n []
#else
            ConP n
#endif


-- | Sequences two Expressions using the '(>>)' operator.
sequenceExps :: Q Exp -> Q Exp -> Q Exp
sequenceExps first second = infixE (Just first) (varE '(>>)) (Just second)

-- | Generates the constraints needed for the declaration of
--   the 'LargeHashable' class. This means that the constraint
--   @LargeHashable $TypeVar$@ is added for every type variable
--   the type has.
#if MIN_VERSION_template_haskell(2,17,0)
makeConstraints :: Cxt -> [TyVarBndr f] -> Q Cxt
#else
makeConstraints :: Cxt -> [TyVarBndr] -> Q Cxt
#endif
makeConstraints context vars = return $ context ++
    map (\v -> (ConT (toLargeHashableClass v)) `AppT` (VarT . varName $ v)) vars
    where
#if MIN_VERSION_template_haskell(2,17,0)
      toLargeHashableClass :: TyVarBndr f -> Name
      toLargeHashableClass var =
        case var of
          (PlainTV _ _) -> ''LargeHashable
          (KindedTV _ _ (AppT (AppT ArrowT StarT) StarT)) -> ''LargeHashable'
          (KindedTV _ _ _) -> ''LargeHashable
#else
      toLargeHashableClass :: TyVarBndr -> Name
      toLargeHashableClass var =
        case var of
          (PlainTV _) -> ''LargeHashable
          (KindedTV _ (AppT (AppT ArrowT StarT) StarT)) -> ''LargeHashable'
          (KindedTV _ _) -> ''LargeHashable
#endif

-- | Returns the 'Name' for a type variable.
#if MIN_VERSION_template_haskell(2,17,0)
varName :: TyVarBndr f -> Name
varName (PlainTV n _) = n
varName (KindedTV n _ _) = n
#else
varName :: TyVarBndr -> Name
varName (PlainTV n) = n
varName (KindedTV n _) = n
#endif

-- | An infinite list of unique names that
--   are used in the generations of patterns.
names :: [String]
names = concat $ map (gen (map (:[]) ['a'..'z'])) [0..]
  where gen :: [String] -> Integer -> [String]
        gen acc 0 = acc
        gen acc n = gen (concat $ map (\q -> map (\c -> c : q) ['a'..'z']) acc) (n - 1)