linear-generics-0.2.3: src/Generics/Linear/TH/Internal.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{- |
Module : Generics.Linear.TH.Internal
Copyright : (c) 2008--2009 Universiteit Utrecht
License : BSD3
Maintainer : generics@haskell.org
Stability : experimental
Portability : non-portable
Template Haskell-related utilities.
-}
module Generics.Linear.TH.Internal where
import Control.Monad (unless)
import Data.Foldable (foldr')
import qualified Data.List as List
import qualified Data.Set as Set
import Data.Set (Set)
import Language.Haskell.TH.Datatype
import Language.Haskell.TH.Datatype.TyVarBndr
import Language.Haskell.TH.Lib
import Language.Haskell.TH.Ppr (pprint)
import Language.Haskell.TH.Syntax hiding (Extension (..))
-------------------------------------------------------------------------------
-- Assorted utilities
-------------------------------------------------------------------------------
-- Note: There are quite a few other utilities in
--
-- generic-deriving: Generics.Deriving.TH.Internal
--
-- Most of the ones that aren't used here have been stripped out.
-- | If a Type is a SigT, returns its kind signature. Otherwise, return *.
typeKind :: Type -> Kind
typeKind (SigT _ k) = k
typeKind _ = starK
-- | Turns
--
-- @
-- [a, b] c
-- @
--
-- into
--
-- @
-- a -> b -> c
-- @
makeFunType :: [Type] -> Type -> Type
makeFunType argTys resTy = foldr' (AppT . AppT ArrowT) resTy argTys
-- | Turns
--
-- @
-- [k1, k2] k3
-- @
--
-- into
--
-- @
-- k1 -> k2 -> k3
-- @
makeFunKind :: [Kind] -> Kind -> Kind
makeFunKind = makeFunType
-- | Remove any outer `SigT` and `ParensT` constructors, and turn
-- an outermost `InfixT` constructor into plain applications.
dustOff :: Type -> Type
dustOff (SigT ty _) = dustOff ty
dustOff (ParensT ty) = dustOff ty
dustOff (InfixT ty1 n ty2) = ConT n `AppT` ty1 `AppT` ty2
dustOff ty = ty
-- | Checks whether a type is an unsaturated type family
-- application.
isUnsaturatedType :: Type -> Q Bool
isUnsaturatedType = go 0 . dustOff
where
-- Expects its argument to be dusted
go :: Int -> Type -> Q Bool
go d t = case t of
ConT tcName -> check d tcName
AppT f _ -> go (d + 1) (dustOff f)
_ -> return False
check :: Int -> Name -> Q Bool
check d tcName = do
mbinders <- getTypeFamilyBinders tcName
return $ case mbinders of
Just bndrs -> length bndrs > d
Nothing -> False
-- | Given a name, check if that name is a type family. If
-- so, return a list of its binders.
getTypeFamilyBinders :: Name -> Q (Maybe [TyVarBndrVis])
getTypeFamilyBinders tcName = do
info <- reify tcName
return $ case info of
FamilyI (OpenTypeFamilyD (TypeFamilyHead _ bndrs _ _)) _
-> Just bndrs
FamilyI (ClosedTypeFamilyD (TypeFamilyHead _ bndrs _ _) _) _
-> Just bndrs
_ -> Nothing
-- | True if the type does not mention the Name
ground :: Type -> Name -> Bool
ground ty name = name `notElem` freeVariables ty
-- | Construct a type via curried application.
applyTyToTys :: Type -> [Type] -> Type
applyTyToTys = List.foldl' AppT
-- | 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]
-- | Checks to see if the last types in a data family instance can be safely eta-
-- reduced (i.e., dropped), given the other types. This checks for three conditions:
--
-- (1) All of the dropped types are type variables
-- (2) All of the dropped types are distinct
-- (3) None of the remaining types mention any of the dropped types
canEtaReduce :: [Type] -> [Type] -> Bool
canEtaReduce remaining dropped =
all isTyVar dropped
-- Make sure not to pass something of type [Type], since Type
-- didn't have an Ord instance until template-haskell-2.10.0.0
&& allDistinct droppedNames
&& not (any (`mentionsName` droppedNames) remaining)
where
droppedNames :: [Name]
droppedNames = map varTToName dropped
-- | 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!"
-- | Is the given type a variable?
isTyVar :: Type -> Bool
isTyVar VarT{} = True
isTyVar (SigT t _) = isTyVar t
isTyVar _ = False
-- | Is the given kind a variable?
isKindVar :: Kind -> Bool
isKindVar = isTyVar
-- | Does the given type mention any of the Names in the list?
mentionsName :: Type -> [Name] -> Bool
mentionsName = go
where
go :: Type -> [Name] -> Bool
go (AppT t1 t2) names = go t1 names || go t2 names
go (SigT t k) names = go t names || go k names
go (VarT n) names = n `elem` names
go _ _ = False
-- | Are all of the items in a list (which have an ordering) distinct?
--
-- This uses Set (as opposed to nub) for better asymptotic time complexity.
allDistinct :: Ord a => [a] -> Bool
allDistinct = allDistinct' Set.empty
where
allDistinct' :: Ord a => Set a -> [a] -> Bool
allDistinct' uniqs (x:xs)
| x `Set.member` uniqs = False
| otherwise = allDistinct' (Set.insert x uniqs) xs
allDistinct' _ _ = True
fst3 :: (a, b, c) -> a
fst3 (a, _, _) = a
snd3 :: (a, b, c) -> b
snd3 (_, b, _) = b
trd3 :: (a, b, c) -> c
trd3 (_, _, c) = c
foldBal :: (a -> a -> a) -> a -> [a] -> a
{-# INLINE foldBal #-} -- inlined to produce specialised code for each op
foldBal op0 x0 xs0 = fold_bal op0 x0 (length xs0) xs0
where
fold_bal op x !n xs = case xs of
[] -> x
[a] -> a
_ -> let !nl = n `div` 2
!nr = n - nl
(l,r) = splitAt nl xs
in fold_bal op x nl l
`op` fold_bal op x nr r
isNewtypeVariant :: DatatypeVariant_ -> Bool
isNewtypeVariant Datatype_ = False
isNewtypeVariant Newtype_ = True
isNewtypeVariant (DataInstance_ {}) = False
isNewtypeVariant (NewtypeInstance_ {}) = True
-- | Indicates whether Generic or Generic1 is being derived.
data GenericClass = Generic | Generic1 deriving Enum
-- | Records information about the type variables of a data type with a
-- 'Generic' or 'Generic1' instance.
data GenericTvbs
-- | Information about a data type with a 'Generic' instance.
= Gen0
{ gen0Tvbs :: [TyVarBndrUnit]
-- ^ All of the type variable arguments to the data type.
}
-- | Information about a data type with a 'Generic1' instance.
| Gen1
{ gen1InitTvbs :: [TyVarBndrUnit]
-- ^ All of the type variable arguments to the data type except the
-- last one. In a @'Generic1' (T a_1 ... a_(n-1))@ instance, the
-- 'gen1InitTvbs' would be @[a_1, ..., a_(n-1)]@.
, gen1LastTvbName :: Name
-- ^ The name of the last type variable argument to the data type.
-- In a @'Generic1' (T a_1 ... a_(n-1))@ instance, the
-- 'gen1LastTvbName' name would be @a_n@.
}
-- | Compute 'GenericTvbs' from a 'GenericClass' and the type variable
-- arguments to a data type.
mkGenericTvbs :: GenericClass -> [Type] -> GenericTvbs
mkGenericTvbs gClass tySynVars =
case gClass of
Generic -> Gen0{gen0Tvbs = freeVariablesWellScoped tySynVars}
Generic1 -> Gen1{ gen1InitTvbs = freeVariablesWellScoped initArgs
, gen1LastTvbName = varTToName lastArg
}
where
-- Everything below is only used for Generic1.
initArgs :: [Type]
initArgs = init tySynVars
lastArg :: Type
lastArg = last tySynVars
-- | Return the type variable arguments to a data type that appear in a
-- 'Generic' or 'Generic1' instance. For a 'Generic' instance, this consists of
-- all the type variable arguments. For a 'Generic1' instance, this consists of
-- all the type variable arguments except for the last one.
genericInitTvbs :: GenericTvbs -> [TyVarBndrUnit]
genericInitTvbs (Gen0{gen0Tvbs = tvbs}) = tvbs
genericInitTvbs (Gen1{gen1InitTvbs = tvbs}) = tvbs
-- | A version of 'DatatypeVariant' in which the data family instance
-- constructors come equipped with the 'ConstructorInfo' of the first
-- constructor in the family instance (for 'Name' generation purposes).
data DatatypeVariant_
= Datatype_
| Newtype_
| DataInstance_ ConstructorInfo
| NewtypeInstance_ ConstructorInfo
-- | One of the last type variables cannot be eta-reduced (see the canEtaReduce
-- function for the criteria it would have to meet).
etaReductionError :: Type -> Q a
etaReductionError instanceType = fail $
"Cannot eta-reduce to an instance of form \n\tinstance (...) => "
++ pprint instanceType
-- | Either the given data type doesn't have enough type variables, or one of
-- the type variables to be eta-reduced cannot realize kind *.
derivingKindError :: Name -> Q a
derivingKindError tyConName = fail
. showString "Cannot derive well-kinded instance of form ‘Generic1 "
. showParen True
( showString (nameBase tyConName)
. showString " ..."
)
. showString "‘\n\tClass Generic1 expects an argument of kind k -> Type"
$ ""
-- | The data type mentions the last type variable in a place other
-- than the last position of a data type in a constructor's field.
outOfPlaceTyVarError :: Q a
outOfPlaceTyVarError = fail
. showString "Constructor must only use its last type variable as"
. showString " the last argument of a data type"
$ ""
-- | The data type mentions the last type variable in a type family
-- application.
typeFamilyApplicationError :: Q a
typeFamilyApplicationError = fail
. showString "Constructor must not apply its last type variable"
. showString " to an unsaturated type family"
$ ""
-- | Cannot have a constructor argument of form (forall a1 ... an. <type>)
-- when deriving Generic(1)
rankNError :: Q a
rankNError = fail "Cannot have polymorphic arguments"
-- | 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.
reifyDataInfo :: Name
-> Q (Name, [Type], [ConstructorInfo], DatatypeVariant_)
reifyDataInfo name = do
do
DatatypeInfo { datatypeContext = ctxt
, datatypeName = parentName
, datatypeInstTypes = tys
, datatypeVariant = variant
, datatypeCons = cons
} <-
fail (ns ++ " Could not reify " ++ nameBase name)
`recover`
reifyDatatype name
variant_ <-
case variant of
Datatype -> return Datatype_
Newtype -> return Newtype_
DataInstance -> return $ DataInstance_ $ headDataFamInstCon parentName cons
NewtypeInstance -> return $ NewtypeInstance_ $ headDataFamInstCon parentName cons
TypeData -> fail $ "Cannot derive Generic instances for TypeData " ++ nameBase name
checkDataContext parentName ctxt
pure (parentName, tys, cons, variant_)
where
ns :: String
ns = "Generics.Linear.TH.reifyDataInfo: "
-- This isn't total, but the API requires that the data family instance have
-- at least one constructor anyways, so this will always succeed.
headDataFamInstCon :: Name -> [ConstructorInfo] -> ConstructorInfo
headDataFamInstCon dataFamName cons =
case cons of
con:_ -> con
[] -> error $ "reified data family instance without a data constructor: "
++ nameBase dataFamName
-- | One cannot derive Generic(1) instance for anything that uses DatatypeContexts,
-- so check to make sure the Cxt field of a datatype is null.
checkDataContext :: Name -> Cxt -> Q ()
checkDataContext _ [] = pure ()
checkDataContext dataName _ = fail $
nameBase dataName ++ " must not have a datatype context"
-- | Deriving Generic(1) doesn't work with ExistentialQuantification or GADTs.
checkExistentialContext :: Name -> [TyVarBndrUnit] -> Cxt -> Q ()
checkExistentialContext conName vars ctxt =
unless (null vars && null ctxt) $ fail $
nameBase conName ++ " must be a vanilla data constructor"
#if !(MIN_VERSION_template_haskell(2,21,0)) && !(MIN_VERSION_th_abstraction(0,6,0))
type TyVarBndrVis = TyVarBndrUnit
bndrReq :: ()
bndrReq = ()
#endif