|
| 1 | +{-# LANGUAGE CPP #-} |
| 2 | +{-# LANGUAGE TypeFamilies #-} |
| 3 | +{-# LANGUAGE UndecidableInstances #-} |
| 4 | + |
| 5 | +-- TODO: finish documentation, including a manual section |
| 6 | + |
| 7 | +module HsBindgen.Runtime.IsForeignType ( |
| 8 | + -- $is-foreign-type |
| 9 | + -- * Class |
| 10 | + IsForeignType |
| 11 | + , BaseForeignType |
| 12 | + , toBaseForeignType |
| 13 | + , fromBaseForeignType |
| 14 | + -- ** Deriving-via |
| 15 | + , ViaNewtype (..) |
| 16 | + -- ** Re-exports |
| 17 | + , module Foreign.C.Types |
| 18 | + , module Foreign.C.Error |
| 19 | +#if MIN_VERSION_base(4, 18, 0) |
| 20 | + , module Foreign.C.ConstPtr |
| 21 | +#endif |
| 22 | + ) where |
| 23 | + |
| 24 | +import Data.Int |
| 25 | +import Data.Kind |
| 26 | +import Data.Word |
| 27 | +import Foreign.C.Error |
| 28 | +import Foreign.C.Types |
| 29 | +import Foreign.Ptr |
| 30 | +import Foreign.StablePtr |
| 31 | + |
| 32 | +#if MIN_VERSION_base(4, 18, 0) |
| 33 | +import Foreign.C.ConstPtr |
| 34 | +#endif |
| 35 | + |
| 36 | +{- $is-foreign-type |
| 37 | +
|
| 38 | +The 'IsForeignType' class broadly captures Haskell's /foreign types/: types that |
| 39 | +are allowed to appear in foreign import declarations. The class can be used to |
| 40 | +map an arbitrary foreign type to its /base/ foreign type: a foreign type with |
| 41 | +all newtypes removed. This mapping can be useful in cases when newtypes are used |
| 42 | +in a foreign import declaration, but their newtype constructors are not in |
| 43 | +scope, in which case a foreign import declaration will fail to compile. |
| 44 | +
|
| 45 | +=== Examples |
| 46 | +
|
| 47 | +For example, if we define @MyInt@ in module @A@: |
| 48 | +
|
| 49 | +> module A where |
| 50 | +> newtype MyInt = MyInt Int |
| 51 | +> deriving via ViaNewtype Int instance IsForeignType |
| 52 | +
|
| 53 | +And then define a module @B@ that only imports the type @MyInt@ from module @A@ |
| 54 | +without importing its constructor. A foreign import declaration like @foo@ in |
| 55 | +module @B@ would fail to compile because the compiler can not see that @MyInt@ |
| 56 | +wraps a foreign type. A foreign import declaration like @bar@ will successfully |
| 57 | +compile because the 'IsForeignType' instance for the @MyInt@ newtype is in scope |
| 58 | +as long as module @B@ (transatively) imports module @A@. We can then define a |
| 59 | +function @bar'@ that maps the foreign base type to the foreign type that we want |
| 60 | +to use. |
| 61 | +
|
| 62 | +> module B where |
| 63 | +> import A (MyInt) |
| 64 | +> |
| 65 | +> foreign import ccall "foo" foo :: MyInt -> IO () |
| 66 | +> |
| 67 | +> foreign import ccall "bar" bar :: |
| 68 | +> BaseForeignType (MyInt -> IO ()) -- equal to Int -> IO () |
| 69 | +> bar' :: MyInt -> IO () |
| 70 | +> bar' = fromForeignBaseType bar |
| 71 | +
|
| 72 | +Alternatively, @foo@ can of course be made to compile successfully by changing |
| 73 | +module @B@ so that it also imports the @MyInt@ construtor from module @A@. This |
| 74 | +is a completely valid solution, but the upside of the 'IsForeignType' class is |
| 75 | +that no fiddling with imports is required to get the right newtype constructors |
| 76 | +in scope. 'IsForeignType' is arguably even nicer when the newtype constructors |
| 77 | +are defined in modules that are depended on transitively, more so if these |
| 78 | +modules are defined other packages that are depended on transitively. |
| 79 | +
|
| 80 | +@hs-bindgen@ uses the 'IsForeignType' class to generate imports that do not |
| 81 | +depend on newtype constructors being in scope. This makes the implementation of |
| 82 | +@hs-bindgen@ considerably simpler: it would otherwise have to track import |
| 83 | +dependencies to get the newtype constructors imports just right. |
| 84 | +
|
| 85 | +=== Relation to the Haskell 2010 Language report |
| 86 | +
|
| 87 | +The "Haskell 2010 Language" report provides grammars for foreign types. These |
| 88 | +grammars are named: |
| 89 | +
|
| 90 | +* /basic foreign types/ (technically not specified as a proper pgrammar) |
| 91 | +* /foreign types/ |
| 92 | +* /marshallable foreign result types/ |
| 93 | +* /marshallable foreign types/ |
| 94 | +
|
| 95 | +We use the term "foreign type" to refer to a type in either of these grammars |
| 96 | +unless we specify that we are talking about the foreign type /grammar/ |
| 97 | +explicitly. |
| 98 | +
|
| 99 | +See the "8.4.2 Foreign Types" section of the report for more information: |
| 100 | +<https://www.haskell.org/onlinereport/haskell2010/haskellch8.html#x15-1560008.4.2> |
| 101 | +
|
| 102 | +This module comes with a set of default 'IsForeignType' instances for these |
| 103 | +grammars, including instances for newtypes from the "Foreign.C" module hierarchy |
| 104 | +of the @base@ package. These instance should be sufficient for most basic use |
| 105 | +cases that do not involve other (custom) newtypes. |
| 106 | +
|
| 107 | +=== Soundness and completeness |
| 108 | +
|
| 109 | +In the "Haskell 2010 Language" report, the grammars for foreign types are |
| 110 | +separate. In our 'IsForeignType' class, we conflate these grammars for the sake |
| 111 | +of simplicity, because it is complex to encode the grammars into separate |
| 112 | +classes precisely. This has the caveat that the class and the default instances |
| 113 | +provided in this module are not /sound/ with respect to foreign types as |
| 114 | +described in the Haskell2010 report. For example: |
| 115 | +
|
| 116 | +> foreign import ... foo :: IO CInt -> IO CInt |
| 117 | +
|
| 118 | +The type of @foo@ is 'IsForeignType', but it is not a valid type for a foreign |
| 119 | +import. 'IO' should (for the most part) only appear in a function result. |
| 120 | +
|
| 121 | +Moreover, instances of the 'IsForeignType' class are not generated magically for |
| 122 | +(custom) newtypes. This means that the class and its default instances provided |
| 123 | +in this module are not /complete/ with respect to foreign types as described in |
| 124 | +the Haskell2010 report. For example: |
| 125 | +
|
| 126 | +> newtype MyCInt = MyCInt CInt |
| 127 | +> foreign import ... bar :: IO MyCInt -> IO CInt |
| 128 | +
|
| 129 | +The type of @bar@ is not 'IsForeignType', but it /is/ a valid type for a foreign |
| 130 | +import, because the compiler can see that @MyCInt@ coerces to a 'CInt'. To make |
| 131 | +@bar@ compile, the user would have to derive an 'IsForeignType' instance using |
| 132 | +@deriving via 'ViaNewtype' 'CInt'@. |
| 133 | +
|
| 134 | +=== Instances generated by @hs-bindgen@ |
| 135 | +
|
| 136 | +@hs-bindgen@ generates 'IsForeignType' instances for the newtypes that it |
| 137 | +generates if the underlying type has a 'IsForeignType' instance as well. It does |
| 138 | +this using newtype-deriving: |
| 139 | +
|
| 140 | +> newtype S = S CInt |
| 141 | +> deriving newtype IsForeignType |
| 142 | +> newtype T a = T a |
| 143 | +> deriving newtype IsForeignType |
| 144 | +> newtype U = U (CChar -> IO CBool) |
| 145 | +> deriving newtype IsForeignType |
| 146 | +
|
| 147 | +If a user wants to provide an instance for a (custom) newtype, then they should |
| 148 | +be able to define one using newtype-deriving, provided the underlying type has a |
| 149 | +'IsForeignType' instance of course. |
| 150 | +-} |
| 151 | + |
| 152 | +{------------------------------------------------------------------------------- |
| 153 | + Class |
| 154 | +-------------------------------------------------------------------------------} |
| 155 | + |
| 156 | +-- | Haskell types that are valid /foreign types/. |
| 157 | +class IsForeignType a where |
| 158 | + -- | |
| 159 | + -- |
| 160 | + -- > 'BaseForeignType' 'Errno' = 'CInt' |
| 161 | + type BaseForeignType a :: Type |
| 162 | + type BaseForeignType a = a |
| 163 | + toBaseType :: a -> BaseForeignType a |
| 164 | + fromBaseType :: BaseForeignType a -> a |
| 165 | + |
| 166 | +-- | Map a type to its base foreign |
| 167 | +toBaseForeignType :: IsForeignType a => a -> BaseForeignType a |
| 168 | +toBaseForeignType = toBaseType |
| 169 | + |
| 170 | +fromBaseForeignType :: IsForeignType a => BaseForeignType a -> a |
| 171 | +fromBaseForeignType = fromBaseType |
| 172 | + |
| 173 | +{------------------------------------------------------------------------------- |
| 174 | + Deriving-via |
| 175 | +-------------------------------------------------------------------------------} |
| 176 | + |
| 177 | +newtype ViaIdentity a = ViaIdentity a |
| 178 | + |
| 179 | +instance IsForeignType (ViaIdentity a) where |
| 180 | + type BaseForeignType (ViaIdentity a) = a |
| 181 | + |
| 182 | + {-# INLINE toBaseType #-} |
| 183 | + toBaseType (ViaIdentity x) = x |
| 184 | + |
| 185 | + {-# INLINE fromBaseType #-} |
| 186 | + fromBaseType x = ViaIdentity x |
| 187 | + |
| 188 | +newtype ViaNewtype a = ViaNewtype a |
| 189 | + |
| 190 | +instance IsForeignType a => IsForeignType (ViaNewtype a) where |
| 191 | + type BaseForeignType (ViaNewtype a) = BaseForeignType a |
| 192 | + |
| 193 | + {-# INLINE toBaseType #-} |
| 194 | + toBaseType (ViaNewtype x) = toBaseType x |
| 195 | + |
| 196 | + {-# INLINE fromBaseType #-} |
| 197 | + fromBaseType x = ViaNewtype (fromBaseType x) |
| 198 | + |
| 199 | +{------------------------------------------------------------------------------- |
| 200 | + Instances: foreign types |
| 201 | +-------------------------------------------------------------------------------} |
| 202 | + |
| 203 | +instance (IsForeignType a, IsForeignType b) => IsForeignType (a -> b) where |
| 204 | + type BaseForeignType (a -> b) = BaseForeignType a -> BaseForeignType b |
| 205 | + |
| 206 | + {-# INLINE toBaseType #-} |
| 207 | + toBaseType f = \x -> toBaseType (f (fromBaseType x)) |
| 208 | + |
| 209 | + {-# INLINE fromBaseType #-} |
| 210 | + fromBaseType f = \x -> fromBaseType (f (toBaseType x)) |
| 211 | + |
| 212 | +{------------------------------------------------------------------------------- |
| 213 | + Instances: marshallable foreign result types |
| 214 | +-------------------------------------------------------------------------------} |
| 215 | + |
| 216 | +deriving via ViaIdentity () instance IsForeignType () |
| 217 | + |
| 218 | +instance IsForeignType a => IsForeignType (IO a) where |
| 219 | + type BaseForeignType (IO a) = IO (BaseForeignType a) |
| 220 | + |
| 221 | + {-# INLINE toBaseType #-} |
| 222 | + toBaseType = fmap toBaseType |
| 223 | + |
| 224 | + {-# INLINE fromBaseType #-} |
| 225 | + fromBaseType = fmap fromBaseType |
| 226 | + |
| 227 | +{------------------------------------------------------------------------------- |
| 228 | + Instances: marshallable foreign types |
| 229 | +-------------------------------------------------------------------------------} |
| 230 | + |
| 231 | +-- NOTE: we use 'ViaIdentity' rather than 'ViaNewtype' to derive 'IsForeignType' |
| 232 | +-- instances even for non-basic foreign types coming from the "Foreign.C" |
| 233 | +-- modules. Most of these types, like 'CInt', are newtypes around basic foreign |
| 234 | +-- types, but the specific basic foreign type depends on the platform\/operating |
| 235 | +-- system. This is not a problem: the constructors for these non-basic types are |
| 236 | +-- going to be in scope anyway. TODO: is this true? |
| 237 | + |
| 238 | +-- === Prelude === |
| 239 | + |
| 240 | +-- Basic foreign types |
| 241 | +deriving via ViaIdentity Char instance IsForeignType Char |
| 242 | +deriving via ViaIdentity Int instance IsForeignType Int |
| 243 | +deriving via ViaIdentity Double instance IsForeignType Double |
| 244 | +deriving via ViaIdentity Float instance IsForeignType Float |
| 245 | +deriving via ViaIdentity Bool instance IsForeignType Bool |
| 246 | + |
| 247 | +-- === Data.Int === |
| 248 | + |
| 249 | +-- Basic foreign types |
| 250 | +deriving via ViaIdentity Int8 instance IsForeignType Int8 |
| 251 | +deriving via ViaIdentity Int16 instance IsForeignType Int16 |
| 252 | +deriving via ViaIdentity Int32 instance IsForeignType Int32 |
| 253 | +deriving via ViaIdentity Int64 instance IsForeignType Int64 |
| 254 | + |
| 255 | +-- === Data.Word === |
| 256 | + |
| 257 | +-- Basic foreign types |
| 258 | +deriving via ViaIdentity Word instance IsForeignType Word |
| 259 | +deriving via ViaIdentity Word8 instance IsForeignType Word8 |
| 260 | +deriving via ViaIdentity Word16 instance IsForeignType Word16 |
| 261 | +deriving via ViaIdentity Word32 instance IsForeignType Word32 |
| 262 | +deriving via ViaIdentity Word64 instance IsForeignType Word64 |
| 263 | + |
| 264 | +-- === Foreign.Ptr === |
| 265 | + |
| 266 | +-- Basic foreign types |
| 267 | +deriving via ViaIdentity (Ptr a) instance IsForeignType (Ptr a) |
| 268 | +deriving via ViaIdentity (FunPtr a) instance IsForeignType (FunPtr a) |
| 269 | + |
| 270 | +-- Newtypes around basic foreign types |
| 271 | +deriving via ViaIdentity IntPtr instance IsForeignType IntPtr |
| 272 | +deriving via ViaIdentity WordPtr instance IsForeignType WordPtr |
| 273 | + |
| 274 | +-- === Foreign.StablePtr === |
| 275 | + |
| 276 | +-- Basic foreign types |
| 277 | +deriving via ViaIdentity (StablePtr a) instance IsForeignType (StablePtr a) |
| 278 | + |
| 279 | +-- === Foreign.C.ConstPtr === |
| 280 | + |
| 281 | +-- Newtypes around basic foreign types |
| 282 | +#if MIN_VERSION_base(4, 18, 0) |
| 283 | +deriving via ViaIdentity (ConstPtr a) instance IsForeignType (ConstPtr a) |
| 284 | +#endif |
| 285 | + |
| 286 | +-- === Foreign.C.Error === |
| 287 | + |
| 288 | +-- Newtypes around basic foreign types |
| 289 | +deriving via ViaNewtype CInt instance IsForeignType Errno |
| 290 | + |
| 291 | +-- === Foreign.C.Types === |
| 292 | + |
| 293 | +-- Newtypes around basic foreign types |
| 294 | +deriving via ViaIdentity CChar instance IsForeignType CChar |
| 295 | +deriving via ViaIdentity CSChar instance IsForeignType CSChar |
| 296 | +deriving via ViaIdentity CUChar instance IsForeignType CUChar |
| 297 | +deriving via ViaIdentity CShort instance IsForeignType CShort |
| 298 | +deriving via ViaIdentity CUShort instance IsForeignType CUShort |
| 299 | +deriving via ViaIdentity CInt instance IsForeignType CInt |
| 300 | +deriving via ViaIdentity CUInt instance IsForeignType CUInt |
| 301 | +deriving via ViaIdentity CLong instance IsForeignType CLong |
| 302 | +deriving via ViaIdentity CULong instance IsForeignType CULong |
| 303 | +deriving via ViaIdentity CPtrdiff instance IsForeignType CPtrdiff |
| 304 | +deriving via ViaIdentity CSize instance IsForeignType CSize |
| 305 | +deriving via ViaIdentity CWchar instance IsForeignType CWchar |
| 306 | +deriving via ViaIdentity CSigAtomic instance IsForeignType CSigAtomic |
| 307 | +deriving via ViaIdentity CLLong instance IsForeignType CLLong |
| 308 | +deriving via ViaIdentity CULLong instance IsForeignType CULLong |
| 309 | +deriving via ViaIdentity CBool instance IsForeignType CBool |
| 310 | +deriving via ViaIdentity CIntPtr instance IsForeignType CIntPtr |
| 311 | +deriving via ViaIdentity CUIntPtr instance IsForeignType CUIntPtr |
| 312 | +deriving via ViaIdentity CIntMax instance IsForeignType CIntMax |
| 313 | +deriving via ViaIdentity CUIntMax instance IsForeignType CUIntMax |
| 314 | + |
| 315 | +-- === Foreign.C.Types : Numeric types === |
| 316 | + |
| 317 | +-- Newtypes around basic foreign types |
| 318 | +deriving via ViaIdentity CClock instance IsForeignType CClock |
| 319 | +deriving via ViaIdentity CTime instance IsForeignType CTime |
| 320 | +deriving via ViaIdentity CUSeconds instance IsForeignType CUSeconds |
| 321 | +deriving via ViaIdentity CSUSeconds instance IsForeignType CSUSeconds |
| 322 | + |
| 323 | +-- === Foreign.C.Types : Floating types === |
| 324 | + |
| 325 | +-- Newtypes around basic foreign types |
| 326 | +deriving via ViaIdentity CFloat instance IsForeignType CFloat |
| 327 | +deriving via ViaIdentity CDouble instance IsForeignType CDouble |
0 commit comments