-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathUtilities.hs
More file actions
255 lines (231 loc) · 9.21 KB
/
Utilities.hs
File metadata and controls
255 lines (231 loc) · 9.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Miscellaneous Template Haskell utilities, added as needed by
-- packages in the th-utilities repo and elsewhere.
module TH.Utilities where
import Control.Monad (foldM)
import Data.Data
import Data.Generics
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import Language.Haskell.TH.Datatype.TyVarBndr (TyVarBndr_, tvName)
import Text.Read (readMaybe)
import TH.FixQ (fixQ)
-- | Get the 'Name' of a 'TyVarBndr'
tyVarBndrName :: TyVarBndr_ flag -> Name
tyVarBndrName = tvName
appsT :: Type -> [Type] -> Type
appsT x [] = x
appsT x (y:xs) = appsT (AppT x y) xs
-- | Breaks a type application like @A b c@ into [A, b, c]. In other
-- words, it descends leftwards down 'AppT' constructors, and yields a
-- list of the results.
unAppsT :: Type -> [Type]
unAppsT = go []
where
go xs (AppT l x) = go (x : xs) l
go xs ty = ty : xs
-- | Given a list of types, produce the type of a tuple of
-- those types. This is analogous to 'tupE' and 'tupP'.
--
-- @
-- tupT [[t|Int|], [t|Char|], [t|Bool]] = [t| (Int, Char, Bool) |]
-- @
--
-- @since FIXME
tupT :: [Q Type] -> Q Type
tupT ts = do
-- We build the expression with a thunk inside that will be filled in with
-- the length of the list once that's been determined. This works
-- efficiently (in one pass) because TH.Type is rather lazy.
(res, !_n) <- fixQ (\ ~(_res, n) -> foldM go (TupleT n, 0) ts)
pure res
where
go (acc, !k) ty = do
ty' <- ty
pure (acc `AppT` ty', k + 1)
-- | Given a list of types, produce the type of a promoted tuple of
-- those types. This is analogous to 'tupE' and 'tupP'.
--
-- @
-- promotedTupT [[t|3|], [t| 'True|], [t|Bool]] = [t| '(3, 'True, Bool) |]
-- @
--
-- @since FIXME
promotedTupT :: [Q Type] -> Q Type
promotedTupT ts = do
-- We build the expression with a thunk inside that will be filled in with
-- the length of the list once that's been determined. This works
-- efficiently (in one pass) because TH.Type is rather lazy.
(res, !_n) <- fixQ (\ ~(_res, n) -> foldM go (PromotedTupleT n, 0) ts)
pure res
where
go (acc, !k) ty = do
ty' <- ty
pure (acc `AppT` ty', k + 1)
-- | Given a 'Type', returns a 'Just' value if it's a named type
-- constructor applied to arguments. This value contains the name of the
-- type and a list of arguments.
typeToNamedCon :: Type -> Maybe (Name, [Type])
#if MIN_VERSION_template_haskell(2,11,0)
typeToNamedCon (InfixT l n r) = Just (n, [l, r])
typeToNamedCon (UInfixT l n r) = Just (n, [l, r])
#endif
typeToNamedCon (unAppsT -> (ConT n : args)) = Just (n, args)
typeToNamedCon _ = Nothing
-- | Expect the provided type to be an application of a regular type to
-- one argument, otherwise fail with a message. This will also work if
-- the name is a promoted data constructor ('PromotedT').
expectTyCon1 :: Name -> Type -> Q Type
expectTyCon1 expected (AppT (ConT n) x) | expected == n = return x
expectTyCon1 expected (AppT (PromotedT n) x) | expected == n = return x
expectTyCon1 expected x = fail $
"Expected " ++ pprint expected ++
", applied to one argument, but instead got " ++ pprint x ++ "."
-- | Expect the provided type to be an application of a regular type to
-- two arguments, otherwise fail with a message. This will also work if
-- the name is a promoted data constructor ('PromotedT').
expectTyCon2 :: Name -> Type -> Q (Type, Type)
expectTyCon2 expected (AppT (AppT (ConT n) x) y) | expected == n = return (x, y)
expectTyCon2 expected (AppT (AppT (PromotedT n) x) y) | expected == n = return (x, y)
#if MIN_VERSION_template_haskell(2,11,0)
expectTyCon2 expected (InfixT x n y) | expected == n = return (x, y)
expectTyCon2 expected (UInfixT x n y) | expected == n = return (x, y)
#endif
expectTyCon2 expected x = fail $
"Expected " ++ pprint expected ++
", applied to two arguments, but instead got " ++ pprint x ++ "."
-- | Given a type, construct the expression (Proxy :: Proxy ty).
proxyE :: TypeQ -> ExpQ
proxyE ty = [| Proxy :: Proxy $(ty) |]
-- | Like the 'everywhere' generic traversal strategy, but skips over
-- strings. This can aid performance of TH traversals quite a bit.
everywhereButStrings :: Data a => (forall b. Data b => b -> b) -> a -> a
everywhereButStrings f =
(f . gmapT (everywhereButStrings f)) `extT` (id :: String -> String)
-- | Like the 'everywhereM' generic traversal strategy, but skips over
-- strings. This can aid performance of TH traversals quite a bit.
everywhereButStringsM :: forall a m. (Data a, Monad m) => GenericM m -> a -> m a
everywhereButStringsM f x = do
x' <- gmapM (everywhereButStringsM f) x
(f `extM` (return :: String -> m String)) x'
-- | Make a 'Name' with a 'NameS' or 'NameQ' flavour, from a 'Name' with
-- any 'NameFlavour'. This may change the meaning of names.
toSimpleName :: Name -> Name
toSimpleName = mkName . pprint
-- | Construct a plain name ('mkName') based on the given name. This is
-- useful for cases where TH doesn't expect a unique name.
dequalify :: Name -> Name
dequalify = mkName . nameBase
-- | Apply 'dequalify' to every type variable.
dequalifyTyVars :: Data a => a -> a
dequalifyTyVars = everywhere (id `extT` modifyType)
where
modifyType (VarT n) = VarT (dequalify n)
modifyType ty = ty
-- | Get the free type variables of a 'Type'.
freeVarsT :: Type -> [Name]
freeVarsT (ForallT tvs _ ty) = filter (`notElem` (map tyVarBndrName tvs)) (freeVarsT ty)
freeVarsT (VarT n) = [n]
freeVarsT ty = concat $ gmapQ (const [] `extQ` freeVarsT) ty
-- | Utility to conveniently handle change to 'InstanceD' API in
-- template-haskell-2.11.0
plainInstanceD :: Cxt -> Type -> [Dec] -> Dec
plainInstanceD =
#if MIN_VERSION_template_haskell(2,11,0)
InstanceD Nothing
#else
InstanceD
#endif
-- | Utility to conveniently handle change to 'InstanceD' API in
-- template-haskell-2.11.0
fromPlainInstanceD :: Dec -> Maybe (Cxt, Type, [Dec])
#if MIN_VERSION_template_haskell(2,11,0)
fromPlainInstanceD (InstanceD _ a b c) = Just (a, b, c)
#else
fromPlainInstanceD (InstanceD a b c) = Just (a, b, c)
#endif
fromPlainInstanceD _ = Nothing
-- | Utility to convert "Data.Typeable" 'TypeRep' to a 'Type'. Note that
-- this function is known to not yet work for many cases, but it does
-- work for normal user datatypes. In future versions this function
-- might have better behavior.
typeRepToType :: TypeRep -> Q Type
typeRepToType tr = do
let (con, args) = splitTyConApp tr
modName = tyConModule con
name pn mn cn = Name (OccName cn) (NameG TcClsName (PkgName pn) (ModName mn))
conName = tyConName con
t | modName == tupleMod = TupleT $ length args
| modName == listMod && conName == listCon = ListT
| modName == typeLitsMod =
case tyConName con of
s@('"':_) -> LitT . StrTyLit $ read s
#if MIN_VERSION_template_haskell(2,18,0)
['\'', c, '\''] -> LitT $ CharTyLit c
#endif
s -> case readMaybe s of
Just n -> LitT $ NumTyLit n
_ -> error $ "Unrecognized type literal name: " ++ s
| otherwise = ConT $ name (tyConPackage con) modName conName
resultArgs <- mapM typeRepToType args
return (appsT t resultArgs)
where
typeLitsMod = tyConModule . typeRepTyCon . typeRep $ Proxy @42
tupleMod = tyConModule . typeRepTyCon . typeRep $ Proxy @((), ())
listMod = tyConModule . typeRepTyCon . typeRep $ Proxy @[()]
listCon = tyConName . typeRepTyCon . typeRep $ Proxy @[()]
-- | Hack to enable putting expressions inside 'lift'-ed TH data. For
-- example, you could do
--
-- @
-- main = print $(lift [ExpLifter [e| 1 + 1 |], ExpLifter [e| 2 |]])
-- @
--
-- Here, 'lift' is working on a value of type @[ExpLifter]@. The code
-- generated by 'lift' constructs a list with the 'ExpLifter'
-- expressions providing the element values.
--
-- Without 'ExpLifter', 'lift' tends to just generate code involving
-- data construction. With 'ExpLifter', you can put more complicated
-- expression into this construction.
--
-- Note that this cannot be used in typed quotes, because 'liftTyped'
-- will throw an exception. This is because this hack is incompatible
-- with the type of 'liftTyped', as it would require the generated
-- code to have type 'ExpLifter'.
data ExpLifter = ExpLifter
#if __GLASGOW_HASKELL__ >= 811
(forall m. Quote m => m Exp)
#else
ExpQ
#endif
deriving (Typeable)
instance Lift ExpLifter where
lift (ExpLifter e) = e
#if MIN_VERSION_template_haskell(2,16,0)
liftTyped = error $ concat
[ "'liftTyped' is not implemented for 'ExpLifter', "
, "because it would require the generated code to have type 'ExpLifter'"
]
#endif
-- | Print splices generated by a TH splice (the printing will happen
-- during compilation, as a GHC warning). Useful for debugging.
--
-- For instance, you can dump splices generated with 'makeLenses' by
-- replacing a top-level invocation of 'makeLenses' in your code with:
--
-- @dumpSplices $ makeLenses ''Foo@
dumpSplices :: DecsQ -> DecsQ
dumpSplices x = do
ds <- x
let code = lines (pprint ds)
reportWarning ("\n" ++ unlines (map (" " ++) code))
return ds