{-# LANGUAGE CPP, FlexibleInstances, NamedFieldPuns,
NoImplicitPrelude, UndecidableInstances #-}
#if __GLASGOW_HASKELL__ >= 800
{-# LANGUAGE TemplateHaskellQuotes #-}
#else
{-# LANGUAGE TemplateHaskell #-}
#endif
#include "overlapping-compat.h"
module Data.Aeson.TH
(
Options(..), SumEncoding(..), defaultOptions, defaultTaggedObject
, deriveJSON
, deriveToJSON
, deriveFromJSON
, mkToJSON
, mkToEncoding
, mkParseJSON
) where
import Control.Applicative ( pure, (<$>), (<*>) )
import Data.Aeson ( toJSON, Object, (.=), (.:), (.:?)
, ToJSON, toEncoding, toJSON
, FromJSON, parseJSON
)
import Data.Aeson.Types ( Value(..), Parser
, Options(..)
, SumEncoding(..)
, defaultOptions
, defaultTaggedObject
)
import Data.Aeson.Types.Internal (Encoding(..))
import Control.Monad ( liftM2, return, mapM, fail )
import Data.Bool ( Bool(False, True), otherwise, (&&), not )
import Data.Either ( Either(Left, Right) )
import Data.Eq ( (==) )
import Data.Function ( ($), (.), flip )
import Data.Functor ( fmap )
import Data.Int ( Int )
import Data.List ( (++), all, any, find, foldl, foldl'
, genericLength , intercalate , intersperse, length, map
, partition, zip
)
import Data.Map ( Map )
import Data.Maybe ( Maybe(Nothing, Just), catMaybes )
import Data.Monoid ( (<>), mconcat )
import Language.Haskell.TH
import Language.Haskell.TH.Syntax ( VarStrictType )
import Prelude ( String, (-), Integer, error, foldr1, fromIntegral
, splitAt, zipWith
)
#if MIN_VERSION_template_haskell(2,8,0) && !(MIN_VERSION_template_haskell(2,10,0))
import Data.Foldable ( foldr' )
import qualified Data.Map as M ( singleton )
import Data.List ( nub )
import Language.Haskell.TH.Syntax ( mkNameG_tc )
import Prelude ( concatMap, uncurry )
#endif
#if MIN_VERSION_template_haskell(2,11,0)
import Prelude ( head )
#endif
import Text.Printf ( printf )
import Text.Show ( show )
import qualified Data.Aeson as A
import qualified Data.Aeson.Encode.Builder as E
import qualified Data.Aeson.Encode.Functions as E
import qualified Data.HashMap.Strict as H ( lookup, toList )
import qualified Data.Map as M ( fromList, findWithDefault )
import qualified Data.Text as T ( Text, pack, unpack )
import qualified Data.Vector as V ( unsafeIndex, null, length, create, fromList )
import qualified Data.Vector.Mutable as VM ( unsafeNew, unsafeWrite )
deriveJSON :: Options
-> Name
-> Q [Dec]
deriveJSON opts name =
liftM2 (++)
(deriveToJSON opts name)
(deriveFromJSON opts name)
deriveToJSON :: Options
-> Name
-> Q [Dec]
deriveToJSON opts name =
withType name $ \name' tvbs cons mbTys -> fmap (:[]) $ fromCons name' tvbs cons mbTys
where
fromCons :: Name -> [TyVarBndr] -> [Con] -> Maybe [Type] -> Q Dec
fromCons name' tvbs cons mbTys = do
(instanceCxt, instanceType) <- buildTypeInstance name' ''ToJSON tvbs mbTys
instanceD (return instanceCxt)
(return instanceType)
[ funD 'toJSON
[ clause []
(normalB $ consToValue opts cons)
[]
]
, funD 'toEncoding
[ clause []
(normalB $ consToEncoding opts cons)
[]
]
]
-- | Generates a lambda expression which encodes the given data type or
-- data family instance constructor as a 'Value'.
mkToJSON :: Options -- ^ Encoding options.
-> Name -- ^ Name of the type to encode.
-> Q Exp
mkToJSON opts name = withType name (\_ _ cons _ -> consToValue opts cons)
-- | Generates a lambda expression which encodes the given data type or
-- data family instance constructor as a JSON string.
mkToEncoding :: Options -- ^ Encoding options.
-> Name -- ^ Name of the type to encode.
-> Q Exp
mkToEncoding opts name = withType name (\_ _ cons _ -> consToEncoding opts cons)
-- | Helper function used by both 'deriveToJSON' and 'mkToJSON'. Generates
-- code to generate a 'Value' of a number of constructors. All constructors
-- must be from the same type.
consToValue :: Options
-- ^ Encoding options.
-> [Con]
-- ^ Constructors for which to generate JSON generating code.
-> Q Exp
consToValue _ [] = error $ "Data.Aeson.TH.consToValue: "
++ "Not a single constructor given!"
-- A single constructor is directly encoded. The constructor itself may be
-- forgotten.
consToValue opts [con] = do
value <- newName "value"
lam1E (varP value) $ caseE (varE value) [argsToValue opts False con]
consToValue opts cons = do
value <- newName "value"
lam1E (varP value) $ caseE (varE value) matches
where
matches
| allNullaryToStringTag opts && all isNullary cons =
[ match (conP conName []) (normalB $ conStr opts conName) []
| con <- cons
, let conName = getConName con
]
| otherwise = [argsToValue opts True con | con <- cons]
conStr :: Options -> Name -> Q Exp
conStr opts = appE [|String|] . conTxt opts
conTxt :: Options -> Name -> Q Exp
conTxt opts = appE [|T.pack|] . conStringE opts
conStringE :: Options -> Name -> Q Exp
conStringE opts = stringE . constructorTagModifier opts . nameBase
-- | Helper function used by both 'deriveToJSON' and 'mkToEncoding'. Generates
-- code to write out a value for a number of constructors. All constructors
-- must be from the same type.
consToEncoding :: Options
-- ^ Encoding options.
-> [Con]
-- ^ Constructors for which to generate JSON generating code.
-> Q Exp
consToEncoding _ [] = error $ "Data.Aeson.TH.consToEncoding: "
++ "Not a single constructor given!"
-- A single constructor is directly encoded. The constructor itself may be
-- forgotten.
consToEncoding opts [con] = do
value <- newName "value"
lam1E (varP value) $ caseE (varE value) [argsToEncoding opts False con]
-- Encode just the name of the constructor of a sum type iff all the
-- constructors are nullary.
consToEncoding opts cons = do
value <- newName "value"
lam1E (varP value) $ caseE (varE value) matches
where
matches
| allNullaryToStringTag opts && all isNullary cons =
[ match (conP conName [])
(normalB $ [|Encoding|] `appE` encStr opts conName) []
| con <- cons
, let conName = getConName con
]
| otherwise = [argsToEncoding opts True con | con <- cons]
encStr :: Options -> Name -> Q Exp
encStr opts = appE [|E.text|] . conTxt opts
-- | If constructor is nullary.
isNullary :: Con -> Bool
isNullary (NormalC _ []) = True
isNullary _ = False
sumToValue :: Options -> Bool -> Name -> Q Exp -> Q Exp
sumToValue opts multiCons conName exp
| multiCons =
case sumEncoding opts of
TwoElemArray ->
[|Array|] `appE` ([|V.fromList|] `appE` listE [conStr opts conName, exp])
TaggedObject{tagFieldName, contentsFieldName} ->
[|A.object|] `appE` listE
[ infixApp [|T.pack tagFieldName|] [|(.=)|] (conStr opts conName)
, infixApp [|T.pack contentsFieldName|] [|(.=)|] exp
]
ObjectWithSingleField ->
[|A.object|] `appE` listE
[ infixApp (conTxt opts conName) [|(.=)|] exp
]
| otherwise = exp
-- | Generates code to generate the JSON encoding of a single constructor.
argsToValue :: Options -> Bool -> Con -> Q Match
-- Nullary constructors. Generates code that explicitly matches against the
-- constructor even though it doesn't contain data. This is useful to prevent
-- type errors.
argsToValue opts multiCons (NormalC conName []) =
match (conP conName [])
(normalB (sumToValue opts multiCons conName [e|toJSON ([] :: [()])|]))
[]
-- Polyadic constructors with special case for unary constructors.
argsToValue opts multiCons (NormalC conName ts) = do
let len = length ts
args <- mapM newName ["arg" ++ show n | n <- [1..len]]
js <- case [[|toJSON|] `appE` varE arg | arg <- args] of
-- Single argument is directly converted.
[e] -> return e
-- Multiple arguments are converted to a JSON array.
es -> do
mv <- newName "mv"
let newMV = bindS (varP mv)
([|VM.unsafeNew|] `appE`
litE (integerL $ fromIntegral len))
stmts = [ noBindS $
[|VM.unsafeWrite|] `appE`
(varE mv) `appE`
litE (integerL ix) `appE`
e
| (ix, e) <- zip [(0::Integer)..] es
]
ret = noBindS $ [|return|] `appE` varE mv
return $ [|Array|] `appE`
(varE 'V.create `appE`
doE (newMV:stmts++[ret]))
match (conP conName $ map varP args)
(normalB $ sumToValue opts multiCons conName js)
[]
-- Records.
argsToValue opts multiCons (RecC conName ts) = case (unwrapUnaryRecords opts, not multiCons, ts) of
(True,True,[(_,st,ty)]) -> argsToValue opts multiCons (NormalC conName [(st,ty)])
_ -> do
args <- mapM newName ["arg" ++ show n | (_, n) <- zip ts [1 :: Integer ..]]
let exp = [|A.object|] `appE` pairs
pairs | omitNothingFields opts = infixApp maybeFields
[|(++)|]
restFields
| otherwise = listE $ map toPair argCons
argCons = zip args ts
maybeFields = [|catMaybes|] `appE` listE (map maybeToPair maybes)
restFields = listE $ map toPair rest
(maybes, rest) = partition isMaybe argCons
maybeToPair (arg, (field, _, _)) =
infixApp (infixE (Just $ toFieldName field)
[|(.=)|]
Nothing)
[|(<$>)|]
(varE arg)
toPair (arg, (field, _, _)) =
infixApp (toFieldName field)
[|(.=)|]
(varE arg)
toFieldName field = [|T.pack|] `appE` fieldLabelExp opts field
match (conP conName $ map varP args)
( normalB
$ if multiCons
then case sumEncoding opts of
TwoElemArray -> [|toJSON|] `appE` tupE [conStr opts conName, exp]
TaggedObject{tagFieldName} ->
[|A.object|] `appE`
-- TODO: Maybe throw an error in case
-- tagFieldName overwrites a field in pairs.
infixApp (infixApp [|T.pack tagFieldName|]
[|(.=)|]
(conStr opts conName))
[|(:)|]
pairs
ObjectWithSingleField ->
[|A.object|] `appE` listE
[ infixApp (conTxt opts conName) [|(.=)|] exp ]
else exp
) []
-- Infix constructors.
argsToValue opts multiCons (InfixC _ conName _) = do
al <- newName "argL"
ar <- newName "argR"
match (infixP (varP al) conName (varP ar))
( normalB
$ sumToValue opts multiCons conName
$ [|toJSON|] `appE` listE [ [|toJSON|] `appE` varE a
| a <- [al,ar]
]
)
[]
-- Existentially quantified constructors.
argsToValue opts multiCons (ForallC _ _ con) =
argsToValue opts multiCons con
#if MIN_VERSION_template_haskell(2,11,0)
-- GADTs.
argsToValue opts multiCons (GadtC conNames ts _) =
argsToValue opts multiCons $ NormalC (head conNames) ts
argsToValue opts multiCons (RecGadtC conNames ts _) =
argsToValue opts multiCons $ RecC (head conNames) ts
#endif
isMaybe :: (a, (b, c, Type)) -> Bool
isMaybe (_, (_, _, AppT (ConT t) _)) = t == ''Maybe
isMaybe _ = False
(<^>) :: ExpQ -> ExpQ -> ExpQ
(<^>) a b = infixApp a [|(<>)|] b
infixr 6 <^>
(<:>) :: ExpQ -> ExpQ -> ExpQ
(<:>) a b = a <^> [|E.char7 ':'|] <^> b
infixr 5 <:>
(<%>) :: ExpQ -> ExpQ -> ExpQ
(<%>) a b = a <^> [|E.char7 ','|] <^> b
infixr 4 <%>
array :: ExpQ -> ExpQ
array exp = [|Encoding|] `appE` ([|E.char7 '['|] <^> exp <^> [|E.char7 ']'|])
object :: ExpQ -> ExpQ
object exp = [|Encoding|] `appE` ([|E.char7 '{'|] <^> exp <^> [|E.char7 '}'|])
sumToEncoding :: Options -> Bool -> Name -> Q Exp -> Q Exp
sumToEncoding opts multiCons conName exp
| multiCons =
let fexp = [|fromEncoding|] `appE` exp in
case sumEncoding opts of
TwoElemArray ->
array (encStr opts conName <%> fexp)
TaggedObject{tagFieldName, contentsFieldName} ->
object $
([|E.text (T.pack tagFieldName)|] <:> encStr opts conName) <%>
([|E.text (T.pack contentsFieldName)|] <:> fexp)
ObjectWithSingleField ->
object (encStr opts conName <:> fexp)
| otherwise = exp
-- | Generates code to generate the JSON encoding of a single constructor.
argsToEncoding :: Options -> Bool -> Con -> Q Match
-- Nullary constructors. Generates code that explicitly matches against the
-- constructor even though it doesn't contain data. This is useful to prevent
-- type errors.
argsToEncoding opts multiCons (NormalC conName []) =
match (conP conName [])
(normalB (sumToEncoding opts multiCons conName [e|toEncoding ([] :: [()])|]))
[]
-- Polyadic constructors with special case for unary constructors.
argsToEncoding opts multiCons (NormalC conName ts) = do
let len = length ts
args <- mapM newName ["arg" ++ show n | n <- [1..len]]
js <- case args of
-- Single argument is directly converted.
[e] -> return ([|toEncoding|] `appE` varE e)
-- Multiple arguments are converted to a JSON array.
es ->
return (array (foldr1 (<%>) [[|E.builder|] `appE` varE x | x <- es]))
match (conP conName $ map varP args)
(normalB $ sumToEncoding opts multiCons conName js)
[]
-- Records.
argsToEncoding opts multiCons (RecC conName ts) = case (unwrapUnaryRecords opts, not multiCons, ts) of
(True,True,[(_,st,ty)]) -> argsToEncoding opts multiCons (NormalC conName [(st,ty)])
_ -> do
args <- mapM newName ["arg" ++ show n | (_, n) <- zip ts [1 :: Integer ..]]
let exp = object objBody
objBody = [|mconcat|] `appE`
([|intersperse (E.char7 ',')|] `appE` pairs)
pairs | omitNothingFields opts = infixApp maybeFields
[|(<>)|]
restFields
| otherwise = listE (map toPair argCons)
argCons = zip args ts
maybeFields = [|catMaybes|] `appE` listE (map maybeToPair maybes)
restFields = listE (map toPair rest)
(maybes, rest) = partition isMaybe argCons
maybeToPair (arg, (field, _, _)) =
infixApp
(infixApp
(infixE
(Just $ toFieldName field <^> [|E.char7 ':'|])
[|(<>)|]
Nothing)
[|(.)|]
[|E.builder|])
[|(<$>)|]
(varE arg)
toPair (arg, (field, _, _)) =
toFieldName field <:> [|E.builder|] `appE` varE arg
toFieldName field = [|E.text|] `appE`
([|T.pack|] `appE` fieldLabelExp opts field)
match (conP conName $ map varP args)
( normalB
$ if multiCons
then case sumEncoding opts of
TwoElemArray -> array $
encStr opts conName <%> [|fromEncoding|] `appE` exp
TaggedObject{tagFieldName} -> object $
([|E.text (T.pack tagFieldName)|] <:>
encStr opts conName) <%>
objBody
ObjectWithSingleField -> object $
encStr opts conName <:> [|fromEncoding|] `appE` exp
else exp
) []
-- Infix constructors.
argsToEncoding opts multiCons (InfixC _ conName _) = do
al <- newName "argL"
ar <- newName "argR"
match (infixP (varP al) conName (varP ar))
( normalB
$ sumToEncoding opts multiCons conName
$ [|toEncoding|] `appE` listE [ [|toJSON|] `appE` varE a
| a <- [al,ar]
]
)
[]
-- Existentially quantified constructors.
argsToEncoding opts multiCons (ForallC _ _ con) =
argsToEncoding opts multiCons con
#if MIN_VERSION_template_haskell(2,11,0)
-- GADTs.
argsToEncoding opts multiCons (GadtC conNames ts _) =
argsToEncoding opts multiCons $ NormalC (head conNames) ts
argsToEncoding opts multiCons (RecGadtC conNames ts _) =
argsToEncoding opts multiCons $ RecC (head conNames) ts
#endif
--------------------------------------------------------------------------------
-- FromJSON
--------------------------------------------------------------------------------
-- | Generates a 'FromJSON' instance declaration for the given data type or
-- data family instance constructor.
deriveFromJSON :: Options
-- ^ Encoding options.
-> Name
-- ^ Name of the type for which to generate a 'FromJSON' instance
-- declaration.
-> Q [Dec]
deriveFromJSON opts name =
withType name $ \name' tvbs cons mbTys -> fmap (:[]) $ fromCons name' tvbs cons mbTys
where
fromCons :: Name -> [TyVarBndr] -> [Con] -> Maybe [Type] -> Q Dec
fromCons name' tvbs cons mbTys = do
(instanceCxt, instanceType) <- buildTypeInstance name' ''FromJSON tvbs mbTys
instanceD (return instanceCxt)
(return instanceType)
[ funD 'parseJSON
[ clause []
(normalB $ consFromJSON name' opts cons)
[]
]
]
-- | Generates a lambda expression which parses the JSON encoding of the given
-- data type or data family instance constructor.
mkParseJSON :: Options -- ^ Encoding options.
-> Name -- ^ Name of the encoded type.
-> Q Exp
mkParseJSON opts name =
withType name (\name' _ cons _ -> consFromJSON name' opts cons)
-- | Helper function used by both 'deriveFromJSON' and 'mkParseJSON'. Generates
-- code to parse the JSON encoding of a number of constructors. All constructors
-- must be from the same type.
consFromJSON :: Name
-- ^ Name of the type to which the constructors belong.
-> Options
-- ^ Encoding options
-> [Con]
-- ^ Constructors for which to generate JSON parsing code.
-> Q Exp
consFromJSON _ _ [] = error $ "Data.Aeson.TH.consFromJSON: "
++ "Not a single constructor given!"
consFromJSON tName opts [con] = do
value <- newName "value"
lam1E (varP value) (parseArgs tName opts con (Right value))
consFromJSON tName opts cons = do
value <- newName "value"
lam1E (varP value) $ caseE (varE value) $
if allNullaryToStringTag opts && all isNullary cons
then allNullaryMatches
else mixedMatches
where
allNullaryMatches =
[ do txt <- newName "txt"
match (conP 'String [varP txt])
(guardedB $
[ liftM2 (,) (normalG $
infixApp (varE txt)
[|(==)|]
([|T.pack|] `appE`
conStringE opts conName)
)
([|pure|] `appE` conE conName)
| con <- cons
, let conName = getConName con
]
++
[ liftM2 (,)
(normalG [|otherwise|])
( [|noMatchFail|]
`appE` (litE $ stringL $ show tName)
`appE` ([|T.unpack|] `appE` varE txt)
)
]
)
[]
, do other <- newName "other"
match (varP other)
(normalB $ [|noStringFail|]
`appE` (litE $ stringL $ show tName)
`appE` ([|valueConName|] `appE` varE other)
)
[]
]
mixedMatches =
case sumEncoding opts of
TaggedObject {tagFieldName, contentsFieldName} ->
parseObject $ parseTaggedObject tagFieldName contentsFieldName
ObjectWithSingleField ->
parseObject $ parseObjectWithSingleField
TwoElemArray ->
[ do arr <- newName "array"
match (conP 'Array [varP arr])
(guardedB $
[ liftM2 (,) (normalG $ infixApp ([|V.length|] `appE` varE arr)
[|(==)|]
(litE $ integerL 2))
(parse2ElemArray arr)
, liftM2 (,) (normalG [|otherwise|])
(([|not2ElemArray|]
`appE` (litE $ stringL $ show tName)
`appE` ([|V.length|] `appE` varE arr)))
]
)
[]
, do other <- newName "other"
match (varP other)
( normalB
$ [|noArrayFail|]
`appE` (litE $ stringL $ show tName)
`appE` ([|valueConName|] `appE` varE other)
)
[]
]
parseObject f =
[ do obj <- newName "obj"
match (conP 'Object [varP obj]) (normalB $ f obj) []
, do other <- newName "other"
match (varP other)
( normalB
$ [|noObjectFail|]
`appE` (litE $ stringL $ show tName)
`appE` ([|valueConName|] `appE` varE other)
)
[]
]
parseTaggedObject typFieldName valFieldName obj = do
conKey <- newName "conKey"
doE [ bindS (varP conKey)
(infixApp (varE obj)
[|(.:)|]
([|T.pack|] `appE` stringE typFieldName))
, noBindS $ parseContents conKey (Left (valFieldName, obj)) 'conNotFoundFailTaggedObject
]
parse2ElemArray arr = do
conKey <- newName "conKey"
conVal <- newName "conVal"
let letIx n ix =
valD (varP n)
(normalB ([|V.unsafeIndex|] `appE`
varE arr `appE`
litE (integerL ix)))
[]
letE [ letIx conKey 0
, letIx conVal 1
]
(caseE (varE conKey)
[ do txt <- newName "txt"
match (conP 'String [varP txt])
(normalB $ parseContents txt
(Right conVal)
'conNotFoundFail2ElemArray
)
[]
, do other <- newName "other"
match (varP other)
( normalB
$ [|firstElemNoStringFail|]
`appE` (litE $ stringL $ show tName)
`appE` ([|valueConName|] `appE` varE other)
)
[]
]
)
parseObjectWithSingleField obj = do
conKey <- newName "conKey"
conVal <- newName "conVal"
caseE ([e|H.toList|] `appE` varE obj)
[ match (listP [tupP [varP conKey, varP conVal]])
(normalB $ parseContents conKey (Right conVal) 'conNotFoundFailObjectSingleField)
[]
, do other <- newName "other"
match (varP other)
(normalB $ [|wrongPairCountFail|]
`appE` (litE $ stringL $ show tName)
`appE` ([|show . length|] `appE` varE other)
)
[]
]
parseContents conKey contents errorFun =
caseE (varE conKey)
[ match wildP
( guardedB $
[ do g <- normalG $ infixApp (varE conKey)
[|(==)|]
([|T.pack|] `appE`
conNameExp opts con)
e <- parseArgs tName opts con contents
return (g, e)
| con <- cons
]
++
[ liftM2 (,)
(normalG [e|otherwise|])
( varE errorFun
`appE` (litE $ stringL $ show tName)
`appE` listE (map ( litE
. stringL
. constructorTagModifier opts
. nameBase
. getConName
) cons
)
`appE` ([|T.unpack|] `appE` varE conKey)
)
]
)
[]
]
parseNullaryMatches :: Name -> Name -> [Q Match]
parseNullaryMatches tName conName =
[ do arr <- newName "arr"
match (conP 'Array [varP arr])
(guardedB $
[ liftM2 (,) (normalG $ [|V.null|] `appE` varE arr)
([|pure|] `appE` conE conName)
, liftM2 (,) (normalG [|otherwise|])
(parseTypeMismatch tName conName
(litE $ stringL "an empty Array")
(infixApp (litE $ stringL $ "Array of length ")
[|(++)|]
([|show . V.length|] `appE` varE arr)
)
)
]
)
[]
, matchFailed tName conName "Array"
]
parseUnaryMatches :: Name -> [Q Match]
parseUnaryMatches conName =
[ do arg <- newName "arg"
match (varP arg)
( normalB $ infixApp (conE conName)
[|(<$>)|]
([|parseJSON|] `appE` varE arg)
)
[]
]
parseRecord :: Options -> Name -> Name -> [VarStrictType] -> Name -> ExpQ
parseRecord opts tName conName ts obj =
foldl' (\a b -> infixApp a [|(<*>)|] b)
(infixApp (conE conName) [|(<$>)|] x)
xs
where
x:xs = [ [|lookupField|]
`appE` (litE $ stringL $ show tName)
`appE` (litE $ stringL $ constructorTagModifier opts $ nameBase conName)
`appE` (varE obj)
`appE` ( [|T.pack|] `appE` fieldLabelExp opts field
)
| (field, _, _) <- ts
]
getValField :: Name -> String -> [MatchQ] -> Q Exp
getValField obj valFieldName matches = do
val <- newName "val"
doE [ bindS (varP val) $ infixApp (varE obj)
[|(.:)|]
([|T.pack|] `appE`
(litE $ stringL valFieldName))
, noBindS $ caseE (varE val) matches
]
-- | Generates code to parse the JSON encoding of a single constructor.
parseArgs :: Name -- ^ Name of the type to which the constructor belongs.
-> Options -- ^ Encoding options.
-> Con -- ^ Constructor for which to generate JSON parsing code.
-> Either (String, Name) Name -- ^ Left (valFieldName, objName) or
-- Right valName
-> Q Exp
-- Nullary constructors.
parseArgs tName _ (NormalC conName []) (Left (valFieldName, obj)) =
getValField obj valFieldName $ parseNullaryMatches tName conName
parseArgs tName _ (NormalC conName []) (Right valName) =
caseE (varE valName) $ parseNullaryMatches tName conName
-- Unary constructors.
parseArgs _ _ (NormalC conName [_]) (Left (valFieldName, obj)) =
getValField obj valFieldName $ parseUnaryMatches conName
parseArgs _ _ (NormalC conName [_]) (Right valName) =
caseE (varE valName) $ parseUnaryMatches conName
-- Polyadic constructors.
parseArgs tName _ (NormalC conName ts) (Left (valFieldName, obj)) =
getValField obj valFieldName $ parseProduct tName conName $ genericLength ts
parseArgs tName _ (NormalC conName ts) (Right valName) =
caseE (varE valName) $ parseProduct tName conName $ genericLength ts
-- Records.
parseArgs tName opts (RecC conName ts) (Left (_, obj)) =
parseRecord opts tName conName ts obj
parseArgs tName opts (RecC conName ts) (Right valName) = case (unwrapUnaryRecords opts,ts) of
(True,[(_,st,ty)])-> parseArgs tName opts (NormalC conName [(st,ty)]) (Right valName)
_ -> do
obj <- newName "recObj"
caseE (varE valName)
[ match (conP 'Object [varP obj]) (normalB $ parseRecord opts tName conName ts obj) []
, matchFailed tName conName "Object"
]
-- Infix constructors. Apart from syntax these are the same as
-- polyadic constructors.
parseArgs tName _ (InfixC _ conName _) (Left (valFieldName, obj)) =
getValField obj valFieldName $ parseProduct tName conName 2
parseArgs tName _ (InfixC _ conName _) (Right valName) =
caseE (varE valName) $ parseProduct tName conName 2
-- Existentially quantified constructors. We ignore the quantifiers
-- and proceed with the contained constructor.
parseArgs tName opts (ForallC _ _ con) contents =
parseArgs tName opts con contents
#if MIN_VERSION_template_haskell(2,11,0)
-- GADTs. We ignore the refined return type and proceed as if it were a
-- NormalC or RecC.
parseArgs tName opts (GadtC conNames ts _) contents =
parseArgs tName opts (NormalC (head conNames) ts) contents
parseArgs tName opts (RecGadtC conNames ts _) contents =
parseArgs tName opts (RecC (head conNames) ts) contents
#endif
-- | Generates code to parse the JSON encoding of an n-ary
-- constructor.
parseProduct :: Name -- ^ Name of the type to which the constructor belongs.
-> Name -- ^ 'Con'structor name.
-> Integer -- ^ 'Con'structor arity.
-> [Q Match]
parseProduct tName conName numArgs =
[ do arr <- newName "arr"
-- List of: "parseJSON (arr `V.unsafeIndex` <IX>)"
let x:xs = [ [|parseJSON|]
`appE`
infixApp (varE arr)
[|V.unsafeIndex|]
(litE $ integerL ix)
| ix <- [0 .. numArgs - 1]
]
match (conP 'Array [varP arr])
(normalB $ condE ( infixApp ([|V.length|] `appE` varE arr)
[|(==)|]
(litE $ integerL numArgs)
)
( foldl' (\a b -> infixApp a [|(<*>)|] b)
(infixApp (conE conName) [|(<$>)|] x)
xs
)
( parseTypeMismatch tName conName
(litE $ stringL $ "Array of length " ++ show numArgs)
( infixApp (litE $ stringL $ "Array of length ")
[|(++)|]
([|show . V.length|] `appE` varE arr)
)
)
)
[]
, matchFailed tName conName "Array"
]
--------------------------------------------------------------------------------
-- Parsing errors
--------------------------------------------------------------------------------
matchFailed :: Name -> Name -> String -> MatchQ
matchFailed tName conName expected = do
other <- newName "other"
match (varP other)
( normalB $ parseTypeMismatch tName conName
(litE $ stringL expected)
([|valueConName|] `appE` varE other)
)
[]
parseTypeMismatch :: Name -> Name -> ExpQ -> ExpQ -> ExpQ
parseTypeMismatch tName conName expected actual =
foldl appE
[|parseTypeMismatch'|]
[ litE $ stringL $ nameBase conName
, litE $ stringL $ show tName
, expected
, actual
]
class (FromJSON a) => LookupField a where
lookupField :: String -> String -> Object -> T.Text -> Parser a
instance OVERLAPPABLE_ (FromJSON a) => LookupField a where
lookupField tName rec obj key =
case H.lookup key obj of
Nothing -> unknownFieldFail tName rec (T.unpack key)
Just v -> parseJSON v
instance (FromJSON a) => LookupField (Maybe a) where
lookupField _ _ = (.:?)
unknownFieldFail :: String -> String -> String -> Parser fail
unknownFieldFail tName rec key =
fail $ printf "When parsing the record %s of type %s the key %s was not present."
rec tName key
noArrayFail :: String -> String -> Parser fail
noArrayFail t o = fail $ printf "When parsing %s expected Array but got %s." t o
noObjectFail :: String -> String -> Parser fail
noObjectFail t o = fail $ printf "When parsing %s expected Object but got %s." t o
firstElemNoStringFail :: String -> String -> Parser fail
firstElemNoStringFail t o = fail $ printf "When parsing %s expected an Array of 2 elements where the first element is a String but got %s at the first element." t o
wrongPairCountFail :: String -> String -> Parser fail
wrongPairCountFail t n =
fail $ printf "When parsing %s expected an Object with a single tag/contents pair but got %s pairs."
t n
noStringFail :: String -> String -> Parser fail
noStringFail t o = fail $ printf "When parsing %s expected String but got %s." t o
noMatchFail :: String -> String -> Parser fail
noMatchFail t o =
fail $ printf "When parsing %s expected a String with the tag of a constructor but got %s." t o
not2ElemArray :: String -> Int -> Parser fail
not2ElemArray t i = fail $ printf "When parsing %s expected an Array of 2 elements but got %i elements" t i
conNotFoundFail2ElemArray :: String -> [String] -> String -> Parser fail
conNotFoundFail2ElemArray t cs o =
fail $ printf "When parsing %s expected a 2-element Array with a tag and contents element where the tag is one of [%s], but got %s."
t (intercalate ", " cs) o
conNotFoundFailObjectSingleField :: String -> [String] -> String -> Parser fail
conNotFoundFailObjectSingleField t cs o =
fail $ printf "When parsing %s expected an Object with a single tag/contents pair where the tag is one of [%s], but got %s."
t (intercalate ", " cs) o
conNotFoundFailTaggedObject :: String -> [String] -> String -> Parser fail
conNotFoundFailTaggedObject t cs o =
fail $ printf "When parsing %s expected an Object with a tag field where the value is one of [%s], but got %s."
t (intercalate ", " cs) o
parseTypeMismatch' :: String -> String -> String -> String -> Parser fail
parseTypeMismatch' conName tName expected actual =
fail $ printf "When parsing the constructor %s of type %s expected %s but got %s."
conName tName expected actual
--------------------------------------------------------------------------------
-- Utility functions
--------------------------------------------------------------------------------
-- | Boilerplate for top level splices.
--
-- The given 'Name' must meet one of two criteria:
--
-- 1. It must be the name of a type constructor of a plain data type or newtype.
-- 2. It must be the name of a data family instance or newtype instance constructor.
-- Any other value will result in an exception.
withType :: Name
-> (Name -> [TyVarBndr] -> [Con] -> Maybe [Type] -> Q a)
-- ^ Function that generates the actual code. Will be applied
-- to the datatype/data family 'Name', type variable binders and
-- constructors extracted from the given 'Name'. If the 'Name' is
-- from a data family instance constructor, it will also have its
-- instantiated types; otherwise, it will be 'Nothing'.
-> Q a
-- ^ Resulting value in the 'Q'uasi monad.
withType name f = do
info <- reify name
case info of
TyConI dec ->
case dec of
#if MIN_VERSION_template_haskell(2,11,0)
DataD _ _ tvbs _ cons _ -> f name tvbs cons Nothing
NewtypeD _ _ tvbs _ con _ -> f name tvbs [con] Nothing
#else
DataD _ _ tvbs cons _ -> f name tvbs cons Nothing
NewtypeD _ _ tvbs con _ -> f name tvbs [con] Nothing
#endif
other -> error $ ns ++ "Unsupported type: " ++ show other
#if MIN_VERSION_template_haskell(2,11,0)
DataConI _ _ parentName -> do
#else
DataConI _ _ parentName _ -> do
#endif
parentInfo <- reify parentName
case parentInfo of
#if MIN_VERSION_template_haskell(2,11,0)
FamilyI (DataFamilyD _ tvbs _) decs ->
#else
FamilyI (FamilyD DataFam _ tvbs _) decs ->
#endif
let instDec = flip find decs $ \dec -> case dec of
#if MIN_VERSION_template_haskell(2,11,0)
DataInstD _ _ _ _ cons _ -> any ((name ==) . getConName) cons
NewtypeInstD _ _ _ _ con _ -> name == getConName con
#else
DataInstD _ _ _ cons _ -> any ((name ==) . getConName) cons
NewtypeInstD _ _ _ con _ -> name == getConName con
#endif
_ -> error $ ns ++ "Must be a data or newtype instance."
in case instDec of
#if MIN_VERSION_template_haskell(2,11,0)
Just (DataInstD _ _ instTys _ cons _) -> f parentName tvbs cons $ Just instTys
Just (NewtypeInstD _ _ instTys _ con _) -> f parentName tvbs [con] $ Just instTys
#else
Just (DataInstD _ _ instTys cons _) -> f parentName tvbs cons $ Just instTys
Just (NewtypeInstD _ _ instTys con _) -> f parentName tvbs [con] $ Just instTys
#endif
_ -> error $ ns ++
"Could not find data or newtype instance constructor."
_ -> error $ ns ++ "Data constructor " ++ show name ++
" is not from a data family instance constructor."
#if MIN_VERSION_template_haskell(2,11,0)
FamilyI DataFamilyD{} _ ->
#else
FamilyI (FamilyD DataFam _ _ _) _ ->
#endif
error $ ns ++
"Cannot use a data family name. Use a data family instance constructor instead."
_ -> error $ ns ++ "I need the name of a plain data type constructor, "
++ "or a data family instance constructor."
where
ns :: String
ns = "Data.Aeson.TH.withType: "
-- | Infer the context and instance head needed for a FromJSON or ToJSON instance.
buildTypeInstance :: Name
-- ^ The type constructor or data family name
-> Name
-- ^ The typeclass name ('ToJSON' or 'FromJSON')
-> [TyVarBndr]
-- ^ The type variables from the data type/data family declaration
-> Maybe [Type]
-- ^ 'Just' the types used to instantiate a data family instance,
-- or 'Nothing' if it's a plain data type
-> Q (Cxt, Type)
-- ^ The resulting 'Cxt' and 'Type' to use in a class instance
-- Plain data type/newtype case
buildTypeInstance tyConName constraint tvbs Nothing =
let varTys :: [Type]
varTys = map tvbToType tvbs
in buildTypeInstanceFromTys tyConName constraint varTys False
-- Data family instance case
--
-- The CPP is present to work around a couple of annoying old GHC bugs.
-- See Note [Polykinded data families in Template Haskell]
buildTypeInstance dataFamName constraint tvbs (Just instTysAndKinds) = do
#if !(MIN_VERSION_template_haskell(2,8,0)) || MIN_VERSION_template_haskell(2,10,0)
let instTys :: [Type]
instTys = zipWith stealKindForType tvbs instTysAndKinds
#else
let kindVarNames :: [Name]
kindVarNames = nub $ concatMap (tyVarNamesOfType . tvbKind) tvbs
-- Gets all of the type/kind variable names mentioned somewhere in a Type.
tyVarNamesOfType :: Type -> [Name]
tyVarNamesOfType = go
where
go :: Type -> [Name]
go (AppT t1 t2) = go t1 ++ go t2
go (SigT t k) = go t ++ go k
go (VarT n) = [n]
go _ = []
numKindVars :: Int
numKindVars = length kindVarNames
givenKinds, givenKinds' :: [Kind]
givenTys :: [Type]
(givenKinds, givenTys) = splitAt numKindVars instTysAndKinds
givenKinds' = map sanitizeStars givenKinds
-- A GHC 7.6-specific bug requires us to replace all occurrences of
-- (ConT GHC.Prim.*) with StarT, or else Template Haskell will reject it.
-- Luckily, (ConT GHC.Prim.*) only seems to occur in this one spot.
sanitizeStars :: Kind -> Kind
sanitizeStars = go
where
go :: Kind -> Kind
go (AppT t1 t2) = AppT (go t1) (go t2)
go (SigT t k) = SigT (go t) (go k)
go (ConT n) | n == starKindName = StarT
go t = t
-- It's quite awkward to import * from GHC.Prim, so we'll just
-- hack our way around it.
starKindName :: Name
starKindName = mkNameG_tc "ghc-prim" "GHC.Prim" "*"
-- Generate a list of fresh names with a common prefix, and numbered suffixes.
newNameList :: String -> Int -> Q [Name]
newNameList prefix n = mapM (newName . (prefix ++) . show) [1..n]
-- If we run this code with GHC 7.8, we might have to generate extra type
-- variables to compensate for any type variables that Template Haskell
-- eta-reduced away.
-- See Note [Polykinded data families in Template Haskell]
xTypeNames <- newNameList "tExtra" (length tvbs - length givenTys)
let xTys :: [Type]
xTys = map VarT xTypeNames
-- ^ Because these type variables were eta-reduced away, we can only
-- determine their kind by using stealKindForType. Therefore, we mark
-- them as VarT to ensure they will be given an explicit kind annotation
-- (and so the kind inference machinery has the right information).
substNameWithKind :: Name -> Kind -> Type -> Type
substNameWithKind n k = substType (M.singleton n k)
substNamesWithKinds :: [(Name, Kind)] -> Type -> Type
substNamesWithKinds nks t = foldr' (uncurry substNameWithKind) t nks
-- The types from the data family instance might not have explicit kind
-- annotations, which the kind machinery needs to work correctly. To
-- compensate, we use stealKindForType to explicitly annotate any
-- types without kind annotations.
instTys :: [Type]
instTys = map (substNamesWithKinds (zip kindVarNames givenKinds'))
-- Note that due to a GHC 7.8-specific bug
-- (see Note [Polykinded data families in Template Haskell]),
-- there may be more kind variable names than there are kinds
-- to substitute. But this is OK! If a kind is eta-reduced, it
-- means that is was not instantiated to something more specific,
-- so we need not substitute it. Using stealKindForType will
-- grab the correct kind.
$ zipWith stealKindForType tvbs (givenTys ++ xTys)
#endif
buildTypeInstanceFromTys dataFamName constraint instTys True
-- For the given Types, generate an instance context and head.
buildTypeInstanceFromTys :: Name
-- ^ The type constructor or data family name
-> Name
-- ^ The typeclass name ('ToJSON' or 'FromJSON')
-> [Type]
-- ^ The types to instantiate the instance with
-> Bool
-- ^ True if it's a data family, False otherwise
-> Q (Cxt, Type)
buildTypeInstanceFromTys tyConName constraint varTysOrig isDataFamily = do
-- Make sure to expand through type/kind synonyms! Otherwise, we won't
-- be able to infer constraints as accurately.
varTysExp <- mapM expandSyn varTysOrig
let preds :: [Maybe Pred]
-- Derive instance constraints for type variables of kind *
preds = map (deriveConstraint constraint) varTysExp
varTys :: [Type]
-- See Note [Kind signatures in derived instances] for an explanation
-- of the isDataFamily check.
varTys =
if isDataFamily
then varTysOrig
else map unSigT varTysOrig
instanceCxt :: Cxt
instanceCxt = catMaybes preds
instanceType :: Type
instanceType = AppT (ConT constraint)
$ applyTyCon tyConName varTys
return (instanceCxt, instanceType)
-- | Attempt to derive a constraint on a Type. If it's of kind *,
-- we give it Just a ToJSON/FromJSON constraint. Otherwise, return Nothing.
deriveConstraint :: Name -> Type -> Maybe Pred
deriveConstraint constraint t
| isTyVar t && hasKindStar t = Just $ applyCon constraint $ varTToName t
| otherwise = Nothing
{-
Note [Polykinded data families in Template Haskell]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In order to come up with the correct instance context and head for an instance, e.g.,
instance C a => C (Data a) where ...
We need to know the exact types and kinds used to instantiate the instance. For
plain old datatypes, this is simple: every type must be a type variable, and
Template Haskell reliably tells us the type variables and their kinds.
Doing the same for data families proves to be much harder for three reasons:
1. On any version of Template Haskell, it may not tell you what an instantiated
type's kind is. For instance, in the following data family instance:
data family Fam (f :: * -> *) (a :: *)
data instance Fam f a
Then if we use TH's reify function, it would tell us the TyVarBndrs of the
data family declaration are:
[KindedTV f (AppT (AppT ArrowT StarT) StarT),KindedTV a StarT]
and the instantiated types of the data family instance are:
[VarT f1,VarT a1]
We can't just pass [VarT f1,VarT a1] to buildTypeInstanceFromTys, since we
have no way of knowing their kinds. Luckily, the TyVarBndrs tell us what the
kind is in case an instantiated type isn't a SigT, so we use the stealKindForType
function to ensure all of the instantiated types are SigTs before passing them
to buildTypeInstanceFromTys.
2. On GHC 7.6 and 7.8, a bug is present in which Template Haskell lists all of
the specified kinds of a data family instance efore any of the instantiated
types. Fortunately, this is easy to deal with: you simply count the number of
distinct kind variables in the data family declaration, take that many elements
from the front of the Types list of the data family instance, substitute the
kind variables with their respective instantiated kinds (which you took earlier),
and proceed as normal.
3. On GHC 7.8, an even uglier bug is present (GHC Trac #9692) in which Template
Haskell might not even list all of the Types of a data family instance, since
they are eta-reduced away! And yes, kinds can be eta-reduced too.
The simplest workaround is to count how many instantiated types are missing from
the list and generate extra type variables to use in their place. Luckily, we
needn't worry much if its kind was eta-reduced away, since using stealKindForType
will get it back.
Note [Kind signatures in derived instances]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is possible to put explicit kind signatures into the derived instances, e.g.,
instance C a => C (Data (f :: * -> *)) where ...
But it is preferable to avoid this if possible. If we come up with an incorrect
kind signature (which is entirely possible, since Template Haskell doesn't always
have the best track record with reifying kind signatures), then GHC will flat-out
reject the instance, which is quite unfortunate.
Plain old datatypes have the advantage that you can avoid using any kind signatures
at all in their instances. This is because a datatype declaration uses all type
variables, so the types that we use in a derived instance uniquely determine their
kinds. As long as we plug in the right types, the kind inferencer can do the rest
of the work. For this reason, we use unSigT to remove all kind signatures before
splicing in the instance context and head.
Data family instances are trickier, since a data family can have two instances that
are distinguished by kind alone, e.g.,
data family Fam (a :: k)
data instance Fam (a :: * -> *)
data instance Fam (a :: *)
If we dropped the kind signatures for C (Fam a), then GHC will have no way of
knowing which instance we are talking about. To avoid this scenario, we always
include explicit kind signatures in data family instances. There is a chance that
the inferred kind signatures will be incorrect, but if so, we can always fall back
on the mk- functions.
-}
-- | If a VarT is missing an explicit kind signature, steal it from a TyVarBndr.
stealKindForType :: TyVarBndr -> Type -> Type
stealKindForType tvb t@VarT{} = SigT t (tvbKind tvb)
stealKindForType _ t = t
-- | Extracts the kind from a type variable binder.
tvbKind :: TyVarBndr -> Kind
#if MIN_VERSION_template_haskell(2,8,0)
tvbKind (PlainTV _ ) = StarT
#else
tvbKind (PlainTV _ ) = StarK
#endif
tvbKind (KindedTV _ k) = k
tvbToType :: TyVarBndr -> Type
tvbToType (PlainTV n) = VarT n
tvbToType (KindedTV n k) = SigT (VarT n) k
-- | Returns True if a Type has kind *.
hasKindStar :: Type -> Bool
hasKindStar VarT{} = True
#if MIN_VERSION_template_haskell(2,8,0)
hasKindStar (SigT _ StarT) = True
#else
hasKindStar (SigT _ StarK) = True
#endif
hasKindStar _ = False
-- | Extract the Name from a type variable. If the argument Type is not a
-- type variable, throw an error.
varTToName :: Type -> Name
varTToName (VarT n) = n
varTToName (SigT t _) = varTToName t
varTToName _ = error "Not a type variable!"
-- | Extracts the name from a constructor.
getConName :: Con -> Name
getConName (NormalC name _) = name
getConName (RecC name _) = name
getConName (InfixC _ name _) = name
getConName (ForallC _ _ con) = getConName con
#if MIN_VERSION_template_haskell(2,11,0)
getConName (GadtC names _ _) = head names
getConName (RecGadtC names _ _) = head names
#endif
-- | Fully applies a type constructor to its type variables.
applyTyCon :: Name -> [Type] -> Type
applyTyCon = foldl' AppT . ConT
-- | Is the given type a variable?
isTyVar :: Type -> Bool
isTyVar (VarT _) = True
isTyVar (SigT t _) = isTyVar t
isTyVar _ = False
-- | Peel off a kind signature from a Type (if it has one).
unSigT :: Type -> Type
unSigT (SigT t _) = t
unSigT t = t
-- | Makes a string literal expression from a constructor's name.
conNameExp :: Options -> Con -> Q Exp
conNameExp opts = litE
. stringL
. constructorTagModifier opts
. nameBase
. getConName
-- | Creates a string literal expression from a record field label.
fieldLabelExp :: Options -- ^ Encoding options
-> Name
-> Q Exp
fieldLabelExp opts = litE . stringL . fieldLabelModifier opts . nameBase
-- | The name of the outermost 'Value' constructor.
valueConName :: Value -> String
valueConName (Object _) = "Object"
valueConName (Array _) = "Array"
valueConName (String _) = "String"
valueConName (Number _) = "Number"
valueConName (Bool _) = "Boolean"
valueConName Null = "Null"
applyCon :: Name -> Name -> Pred
applyCon con t =
#if MIN_VERSION_template_haskell(2,10,0)
AppT (ConT con) (VarT t)
#else
ClassP con [VarT t]
#endif
-------------------------------------------------------------------------------
-- Expanding type synonyms
-------------------------------------------------------------------------------
-- | Expands all type synonyms in a type. Written by Dan Rosén in the
-- @genifunctors@ package (licensed under BSD3).
expandSyn :: Type -> Q Type
expandSyn (ForallT tvs ctx t) = fmap (ForallT tvs ctx) $ expandSyn t
expandSyn t@AppT{} = expandSynApp t []
expandSyn t@ConT{} = expandSynApp t []
expandSyn (SigT t k) = do t' <- expandSyn t
k' <- expandSynKind k
return (SigT t' k')
expandSyn t = return t
expandSynKind :: Kind -> Q Kind
#if MIN_VERSION_template_haskell(2,8,0)
expandSynKind = expandSyn
#else
expandSynKind = return -- There are no kind synonyms to deal with
#endif
expandSynApp :: Type -> [Type] -> Q Type
expandSynApp (AppT t1 t2) ts = do
t2' <- expandSyn t2
expandSynApp t1 (t2':ts)
expandSynApp (ConT n) ts | nameBase n == "[]" = return $ foldl' AppT ListT ts
expandSynApp t@(ConT n) ts = do
info <- reify n
case info of
TyConI (TySynD _ tvs rhs) ->
let (ts', ts'') = splitAt (length tvs) ts
subs = mkSubst tvs ts'
rhs' = substType subs rhs
in expandSynApp rhs' ts''
_ -> return $ foldl' AppT t ts
expandSynApp t ts = do
t' <- expandSyn t
return $ foldl' AppT t' ts
type TypeSubst = Map Name Type
mkSubst :: [TyVarBndr] -> [Type] -> TypeSubst
mkSubst vs ts =
let vs' = map un vs
un (PlainTV v) = v
un (KindedTV v _) = v
in M.fromList $ zip vs' ts
substType :: TypeSubst -> Type -> Type
substType subs (ForallT v c t) = ForallT v c $ substType subs t
substType subs t@(VarT n) = M.findWithDefault t n subs
substType subs (AppT t1 t2) = AppT (substType subs t1) (substType subs t2)
substType subs (SigT t k) = SigT (substType subs t)
#if MIN_VERSION_template_haskell(2,8,0)
(substType subs k)
#else
k
#endif
substType _ t = t