{-# LANGUAGE CPP #-}
{-# LANGUAGE PatternGuards #-}
#ifdef TRUSTWORTHY
# if MIN_VERSION_template_haskell(2,12,0)
{-# LANGUAGE Safe #-}
# else
{-# LANGUAGE Trustworthy #-}
# endif
#endif
#include "lens-common.h"
module Control.Lens.TH
(
makeLenses, makeLensesFor
, makeClassy, makeClassyFor, makeClassy_
, makeFields
, makeFieldsNoPrefix
, makePrisms
, makeClassyPrisms
, makeWrapped
, declareLenses, declareLensesFor
, declareClassy, declareClassyFor
, declareFields
, declarePrisms
, declareWrapped
, makeLensesWith
, declareLensesWith
, LensRules
, lensRules
, lensRulesFor
, classyRules
, classyRules_
, defaultFieldRules
, camelCaseFields
, classUnderscoreNoPrefixFields
, underscoreFields
, abbreviatedFields
, lensField
, FieldNamer
, DefName(..)
, lensClass
, ClassyNamer
, simpleLenses
, createClass
, generateSignatures
, generateUpdateableOptics
, generateLazyPatterns
, generateRecordSyntax
, underscoreNoPrefixNamer
, lookingupNamer
, mappingNamer
, camelCaseNamer
, classUnderscoreNoPrefixNamer
, underscoreNamer
, abbreviatedNamer
) where
import Prelude ()
import Control.Monad.Trans.Class
import Control.Monad.Trans.State
import Control.Monad.Trans.Writer
import Control.Lens.Fold
import Control.Lens.Getter
import Control.Lens.Lens
import Control.Lens.Setter
import Control.Lens.Traversal
import Control.Lens.Internal.Prelude as Prelude
import Control.Lens.Internal.TH
import Control.Lens.Internal.FieldTH
import Control.Lens.Internal.PrismTH
import Control.Lens.Wrapped ()
import Control.Lens.Type ()
import Data.Char (toLower, toUpper, isUpper)
import Data.Foldable hiding (concat, any)
import qualified Data.List as List
import qualified Data.Map as Map
import Data.Map (Map)
import Data.Maybe (maybeToList)
import qualified Data.Set as Set
import Data.Set (Set)
import Data.Traversable hiding (mapM)
import Language.Haskell.TH.Datatype
import Language.Haskell.TH.Lens
import Language.Haskell.TH.Lib
import Language.Haskell.TH.Syntax hiding (lift)
simpleLenses :: Lens' LensRules Bool
simpleLenses f r = fmap (\x -> r { _simpleLenses = x}) (f (_simpleLenses r))
generateSignatures :: Lens' LensRules Bool
generateSignatures f r =
fmap (\x -> r { _generateSigs = x}) (f (_generateSigs r))
generateUpdateableOptics :: Lens' LensRules Bool
generateUpdateableOptics f r =
fmap (\x -> r { _allowUpdates = x}) (f (_allowUpdates r))
generateLazyPatterns :: Lens' LensRules Bool
generateLazyPatterns f r =
fmap (\x -> r { _lazyPatterns = x}) (f (_lazyPatterns r))
generateRecordSyntax :: Lens' LensRules Bool
generateRecordSyntax f r =
fmap (\x -> r {_recordSyntax = x}) (f (_recordSyntax r))
createClass :: Lens' LensRules Bool
createClass f r =
fmap (\x -> r { _generateClasses = x}) (f (_generateClasses r))
lensField :: Lens' LensRules FieldNamer
lensField f r = fmap (\x -> r { _fieldToDef = x}) (f (_fieldToDef r))
lensClass :: Lens' LensRules ClassyNamer
lensClass f r = fmap (\x -> r { _classyLenses = x }) (f (_classyLenses r))
lensRules :: LensRules
lensRules = LensRules
{ _simpleLenses = False
, _generateSigs = True
, _generateClasses = False
, _allowIsos = True
, _allowUpdates = True
, _lazyPatterns = False
, _recordSyntax = False
, _classyLenses = const Nothing
, _fieldToDef = underscoreNoPrefixNamer
}
underscoreNoPrefixNamer :: FieldNamer
underscoreNoPrefixNamer _ _ n =
case nameBase n of
'_':x:xs -> [TopName (mkName (toLower x:xs))]
_ -> []
lensRulesFor ::
[(String, String)] ->
LensRules
lensRulesFor fields = lensRules & lensField .~ lookingupNamer fields
lookingupNamer :: [(String,String)] -> FieldNamer
lookingupNamer kvs _ _ field =
[ TopName (mkName v) | (k,v) <- kvs, k == nameBase field]
mappingNamer :: (String -> [String])
-> FieldNamer
mappingNamer mapper _ _ = fmap (TopName . mkName) . mapper . nameBase
classyRules :: LensRules
classyRules = LensRules
{ _simpleLenses = True
, _generateSigs = True
, _generateClasses = True
, _allowIsos = False
, _allowUpdates = True
, _lazyPatterns = False
, _recordSyntax = False
, _classyLenses = \n ->
case nameBase n of
x:xs -> Just (mkName ("Has" ++ x:xs), mkName (toLower x:xs))
[] -> Nothing
, _fieldToDef = underscoreNoPrefixNamer
}
classyRulesFor
:: (String -> Maybe (String, String)) ->
[(String, String)] ->
LensRules
classyRulesFor classFun fields = classyRules
& lensClass .~ (over (mapped . both) mkName . classFun . nameBase)
& lensField .~ lookingupNamer fields
classyRules_ :: LensRules
classyRules_
= classyRules & lensField .~ \_ _ n -> [TopName (mkName ('_':nameBase n))]
makeLenses :: Name -> DecsQ
makeLenses = makeFieldOptics lensRules
makeClassy :: Name -> DecsQ
makeClassy = makeFieldOptics classyRules
makeClassy_ :: Name -> DecsQ
makeClassy_ = makeFieldOptics classyRules_
makeLensesFor :: [(String, String)] -> Name -> DecsQ
makeLensesFor fields = makeFieldOptics (lensRulesFor fields)
makeClassyFor :: String -> String -> [(String, String)] -> Name -> DecsQ
makeClassyFor clsName funName fields = makeFieldOptics $
classyRulesFor (const (Just (clsName, funName))) fields
makeLensesWith :: LensRules -> Name -> DecsQ
makeLensesWith = makeFieldOptics
declareLenses :: DecsQ -> DecsQ
declareLenses
= declareLensesWith
$ lensRules
& lensField .~ \_ _ n -> [TopName n]
declareLensesFor :: [(String, String)] -> DecsQ -> DecsQ
declareLensesFor fields
= declareLensesWith
$ lensRulesFor fields
& lensField .~ \_ _ n -> [TopName n]
declareClassy :: DecsQ -> DecsQ
declareClassy
= declareLensesWith
$ classyRules
& lensField .~ \_ _ n -> [TopName n]
declareClassyFor ::
[(String, (String, String))] -> [(String, String)] -> DecsQ -> DecsQ
declareClassyFor classes fields
= declareLensesWith
$ classyRulesFor (`Prelude.lookup`classes) fields
& lensField .~ \_ _ n -> [TopName n]
declarePrisms :: DecsQ -> DecsQ
declarePrisms = declareWith $ \dec -> do
emit =<< liftDeclare (makeDecPrisms True dec)
return dec
declareWrapped :: DecsQ -> DecsQ
declareWrapped = declareWith $ \dec -> do
maybeDecs <- liftDeclare $ do
inf <- normalizeDec dec
makeWrappedForDatatypeInfo inf
forM_ maybeDecs emit
return dec
declareFields :: DecsQ -> DecsQ
declareFields = declareLensesWith defaultFieldRules
declareLensesWith :: LensRules -> DecsQ -> DecsQ
declareLensesWith rules = declareWith $ \dec -> do
emit =<< lift (makeFieldOpticsForDec' rules dec)
return $ stripFields dec
freshMap :: Set Name -> Q (Map Name Name)
freshMap ns = Map.fromList <$> for (toList ns) (\ n -> (,) n <$> newName (nameBase n))
apps :: Type -> [Type] -> Type
apps = Prelude.foldl AppT
makeWrapped :: Name -> DecsQ
makeWrapped nm = do
inf <- reifyDatatype nm
maybeDecs <- makeWrappedForDatatypeInfo inf
maybe (fail "makeWrapped: Unsupported data type") return maybeDecs
makeWrappedForDatatypeInfo :: DatatypeInfo -> Q (Maybe [Dec])
makeWrappedForDatatypeInfo dataInfo@(DatatypeInfo{datatypeCons = cons})
| [conInfo@(ConstructorInfo{constructorFields = fields})] <- cons
, [field] <- fields
= do wrapped <- makeWrappedInstance dataInfo conInfo field
rewrapped <- makeRewrappedInstance dataInfo
return (Just [rewrapped, wrapped])
| otherwise = return Nothing
makeRewrappedInstance :: DatatypeInfo -> DecQ
makeRewrappedInstance dataInfo = do
t <- varT <$> newName "t"
let typeArgs = map (view name) (datatypeVars dataInfo)
typeArgs' <- do
m <- freshMap (Set.fromList typeArgs)
return (substTypeVars m typeArgs)
let appliedType = return (applyDatatypeToArgs dataInfo (map VarT typeArgs))
appliedType' = return (applyDatatypeToArgs dataInfo (map VarT typeArgs'))
eq = AppT. AppT EqualityT <$> appliedType' <*> t
klass = conT rewrappedTypeName `appsT` [appliedType, t]
instanceD (cxt [eq]) klass []
makeWrappedInstance :: DatatypeInfo -> ConstructorInfo -> Type -> DecQ
makeWrappedInstance dataInfo conInfo fieldType = do
let conName = constructorName conInfo
let typeArgs = toListOf typeVars (datatypeVars dataInfo)
let appliedType = applyDatatypeToArgs dataInfo (map VarT typeArgs)
let unwrappedATF = tySynInstDCompat unwrappedTypeName Nothing
[return appliedType] (return fieldType)
let klass = conT wrappedTypeName `appT` return appliedType
let wrapFun = conE conName
let unwrapFun = newName "x" >>= \x -> lam1E (conP conName [varP x]) (varE x)
let body = appsE [varE isoValName, unwrapFun, wrapFun]
let isoMethod = funD _wrapped'ValName [clause [] (normalB body) []]
instanceD (cxt []) klass [unwrappedATF, isoMethod]
applyDatatypeToArgs :: DatatypeInfo -> [Type] -> Type
applyDatatypeToArgs di@(DatatypeInfo { datatypeName = nm
, datatypeVars = vars
, datatypeInstTypes = instTypes
}) args =
apps (ConT nm) $
dropSigsIfNonDataFam di $
applySubstitution (Map.fromList (zip (map tvName vars) args)) instTypes
overHead :: (a -> a) -> [a] -> [a]
overHead _ [] = []
overHead f (x:xs) = f x : xs
underscoreFields :: LensRules
underscoreFields = defaultFieldRules & lensField .~ underscoreNamer
underscoreNamer :: FieldNamer
underscoreNamer _ _ field = maybeToList $ do
_ <- prefix field'
method <- niceLens
cls <- classNaming
return (MethodName (mkName cls) (mkName method))
where
field' = nameBase field
prefix ('_':xs) | '_' `List.elem` xs = Just (takeWhile (/= '_') xs)
prefix _ = Nothing
niceLens = prefix field' <&> \n -> drop (length n + 2) field'
classNaming = niceLens <&> ("Has_" ++)
camelCaseFields :: LensRules
camelCaseFields = defaultFieldRules
camelCaseNamer :: FieldNamer
camelCaseNamer tyName fields field = maybeToList $ do
fieldPart <- List.stripPrefix expectedPrefix (nameBase field)
method <- computeMethod fieldPart
let cls = "Has" ++ fieldPart
return (MethodName (mkName cls) (mkName method))
where
expectedPrefix = optUnderscore ++ overHead toLower (nameBase tyName)
optUnderscore = ['_' | any (List.isPrefixOf "_" . nameBase) fields ]
computeMethod (x:xs) | isUpper x = Just (toLower x : xs)
computeMethod _ = Nothing
classUnderscoreNoPrefixFields :: LensRules
classUnderscoreNoPrefixFields =
defaultFieldRules & lensField .~ classUnderscoreNoPrefixNamer
classUnderscoreNoPrefixNamer :: FieldNamer
classUnderscoreNoPrefixNamer _ _ field = maybeToList $ do
fieldUnprefixed <- List.stripPrefix "_" (nameBase field)
let className = "Has" ++ overHead toUpper fieldUnprefixed
methodName = fieldUnprefixed
return (MethodName (mkName className) (mkName methodName))
abbreviatedFields :: LensRules
abbreviatedFields = defaultFieldRules { _fieldToDef = abbreviatedNamer }
abbreviatedNamer :: FieldNamer
abbreviatedNamer _ fields field = maybeToList $ do
fieldPart <- stripMaxLc (nameBase field)
method <- computeMethod fieldPart
let cls = "Has" ++ fieldPart
return (MethodName (mkName cls) (mkName method))
where
stripMaxLc f = do x <- List.stripPrefix optUnderscore f
case break isUpper x of
(p,s) | List.null p || List.null s -> Nothing
| otherwise -> Just s
optUnderscore = ['_' | any (List.isPrefixOf "_" . nameBase) fields ]
computeMethod (x:xs) | isUpper x = Just (toLower x : xs)
computeMethod _ = Nothing
makeFields :: Name -> DecsQ
makeFields = makeFieldOptics camelCaseFields
makeFieldsNoPrefix :: Name -> DecsQ
makeFieldsNoPrefix = makeFieldOptics classUnderscoreNoPrefixFields
defaultFieldRules :: LensRules
defaultFieldRules = LensRules
{ _simpleLenses = True
, _generateSigs = True
, _generateClasses = True
, _allowIsos = False
, _allowUpdates = True
, _lazyPatterns = False
, _recordSyntax = False
, _classyLenses = const Nothing
, _fieldToDef = camelCaseNamer
}
declareWith :: (Dec -> Declare Dec) -> DecsQ -> DecsQ
declareWith fun = (runDeclare . traverseDataAndNewtype fun =<<)
type Declare = WriterT (Endo [Dec]) (StateT (Set Name) Q)
liftDeclare :: Q a -> Declare a
liftDeclare = lift . lift
runDeclare :: Declare [Dec] -> DecsQ
runDeclare dec = do
(out, endo) <- evalStateT (runWriterT dec) Set.empty
return $ out ++ appEndo endo []
emit :: [Dec] -> Declare ()
emit decs = tell $ Endo (decs++)
traverseDataAndNewtype :: (Applicative f) => (Dec -> f Dec) -> [Dec] -> f [Dec]
traverseDataAndNewtype f = traverse go
where
go dec = case dec of
DataD{} -> f dec
NewtypeD{} -> f dec
DataInstD{} -> f dec
NewtypeInstD{} -> f dec
InstanceD moverlap ctx inst body -> InstanceD moverlap ctx inst <$> traverse go body
_ -> pure dec
stripFields :: Dec -> Dec
stripFields dec = case dec of
DataD ctx tyName tyArgs kind cons derivings ->
DataD ctx tyName tyArgs kind (map deRecord cons) derivings
NewtypeD ctx tyName tyArgs kind con derivings ->
NewtypeD ctx tyName tyArgs kind (deRecord con) derivings
DataInstD ctx tyName tyArgs kind cons derivings ->
DataInstD ctx tyName tyArgs kind (map deRecord cons) derivings
NewtypeInstD ctx tyName tyArgs kind con derivings ->
NewtypeInstD ctx tyName tyArgs kind (deRecord con) derivings
_ -> dec
deRecord :: Con -> Con
deRecord con@NormalC{} = con
deRecord con@InfixC{} = con
deRecord (ForallC tyVars ctx con) = ForallC tyVars ctx $ deRecord con
deRecord (RecC conName fields) = NormalC conName (map dropFieldName fields)
deRecord con@GadtC{} = con
deRecord (RecGadtC ns fields retTy) = GadtC ns (map dropFieldName fields) retTy
dropFieldName :: VarBangType -> BangType
dropFieldName (_, str, typ) = (str, typ)