Skip to content

Commit aeb90ed

Browse files
committed
WIP: IsForeignType
1 parent 90b0aa2 commit aeb90ed

File tree

247 files changed

+25928
-4311
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

247 files changed

+25928
-4311
lines changed

hs-bindgen-runtime/hs-bindgen-runtime.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ library
5555
HsBindgen.Runtime.FunPtr
5656
HsBindgen.Runtime.HasCField
5757
HsBindgen.Runtime.IncompleteArray
58+
HsBindgen.Runtime.IsForeignType
5859
HsBindgen.Runtime.LibC
5960
HsBindgen.Runtime.Marshal
6061
HsBindgen.Runtime.Prelude

hs-bindgen-runtime/src/HsBindgen/Runtime/Block.hs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
{-# LANGUAGE UndecidableInstances #-}
2+
13
-- | Bare-bones support for blocks
24
--
35
-- TODO: Ideally we would at least support @Block_copy@ and @Block_release@.
@@ -7,7 +9,9 @@ module HsBindgen.Runtime.Block (
79
Block(..)
810
) where
911

10-
import Foreign
12+
import Foreign (Ptr)
13+
14+
import HsBindgen.Runtime.IsForeignType (IsForeignType, ViaNewtype (..))
1115

1216
{-------------------------------------------------------------------------------
1317
Definition
@@ -27,3 +31,5 @@ import Foreign
2731
--
2832
-- > newtype VarCounter = VarCounter (Block (CInt -> IO CInt))
2933
newtype Block t = Block (Ptr ())
34+
35+
deriving via ViaNewtype (Ptr ()) instance IsForeignType (Block t)
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
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

Comments
 (0)