Skip to content

Commit d3d301b

Browse files
committed
Initial implementation of typesafe marshaling
See this issue: ghcjs/ghcjs#419
1 parent d9b10ff commit d3d301b

File tree

17 files changed

+391
-157
lines changed

17 files changed

+391
-157
lines changed

Data/JSString.hs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,6 @@ import Data.JSString.Internal.Fusion (stream, unstream)
166166
import qualified Data.JSString.Internal.Fusion as S
167167
import qualified Data.JSString.Internal.Fusion.Common as S
168168

169-
getJSVal :: JSString -> JSVal
170-
getJSVal (JSString x) = x
171-
{-# INLINE getJSVal #-}
172-
173169
instance Exts.IsString JSString where
174170
fromString = pack
175171

Data/JSString/Internal/Type.hs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{-# LANGUAGE CPP, DeriveDataTypeable, UnboxedTuples, MagicHash,
2-
BangPatterns, ForeignFunctionInterface, JavaScriptFFI #-}
2+
BangPatterns, ForeignFunctionInterface, JavaScriptFFI,
3+
GeneralizedNewtypeDeriving #-}
34
{-# OPTIONS_HADDOCK not-home #-}
45
module Data.JSString.Internal.Type ( JSString(..)
56
, empty
@@ -42,9 +43,7 @@ import GHCJS.Internal.Types
4243

4344
-- | A wrapper around a JavaScript string
4445
newtype JSString = JSString JSVal
45-
instance IsJSVal JSString
46-
47-
instance NFData JSString where rnf !x = ()
46+
deriving (IsJSVal, Typeable, NFData)
4847

4948
foreign import javascript unsafe
5049
"$r = '';" js_empty :: JSString

GHCJS/Foreign.hs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
{-# LANGUAGE DefaultSignatures #-}
44
{- | Basic interop between Haskell and JavaScript.
55
6-
The principal type here is 'JSVal', which is a lifted type that contains
7-
a JavaScript reference. The 'JSVal' type is parameterized with one phantom
8-
type, and GHCJS.Types defines several type synonyms for specific variants.
6+
The principle type here is 'JSVal', which contains a JavaScript
7+
value.
98
10-
The code in this module makes no assumptions about 'JSVal a' types.
9+
The code in this module makes no assumptions about 'JSVal' types.
1110
Operations that can result in a JS exception that can kill a Haskell thread
1211
are marked unsafe (for example if the 'JSVal' contains a null or undefined
1312
value). There are safe variants where the JS exception is propagated as
@@ -67,8 +66,8 @@ module GHCJS.Foreign ( jsTrue
6766
, getPropMaybe, unsafeGetPropMaybe
6867
, setProp, unsafeSetProp
6968
, listProps -}
70-
, jsTypeOf, JSType(..)
71-
, jsonTypeOf, JSONType(..)
69+
, jsTypeOf, JSTypeOf(..)
70+
, jsonTypeOf, JSONTypeOf(..)
7271
{- , wrapBuffer, wrapMutableBuffer
7372
, byteArrayJSVal, mutableByteArrayJSVal
7473
, bufferByteString, byteArrayByteString

GHCJS/Foreign/Internal.hs

Lines changed: 21 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,9 @@
22
UnboxedTuples, DeriveDataTypeable, GHCForeignImportPrim,
33
MagicHash, FlexibleInstances, BangPatterns, Rank2Types, CPP #-}
44

5-
{- | Basic interop between Haskell and JavaScript.
6-
7-
The principal type here is 'JSVal', which is a lifted type that contains
8-
a JavaScript reference. The 'JSVal' type is parameterized with one phantom
9-
type, and GHCJS.Types defines several type synonyms for specific variants.
10-
11-
The code in this module makes no assumptions about 'JSVal a' types.
12-
Operations that can result in a JS exception that can kill a Haskell thread
13-
are marked unsafe (for example if the 'JSVal' contains a null or undefined
14-
value). There are safe variants where the JS exception is propagated as
15-
a Haskell exception, so that it can be handled on the Haskell side.
16-
17-
For more specific types, like 'JSArray' or 'JSBool', the code assumes that
18-
the contents of the 'JSVal' actually is a JavaScript array or bool value.
19-
If it contains an unexpected value, the code can result in exceptions that
20-
kill the Haskell thread, even for functions not marked unsafe.
21-
22-
The code makes use of `foreign import javascript', enabled with the
23-
`JavaScriptFFI` extension, available since GHC 7.8. There are three different
24-
safety levels:
25-
26-
* unsafe: The imported code is run directly. returning an incorrectly typed
27-
value leads to undefined behaviour. JavaScript exceptions in the foreign
28-
code kill the Haskell thread.
29-
* safe: Returned values are replaced with a default value if they have
30-
the wrong type. JavaScript exceptions are caught and propagated as
31-
Haskell exceptions ('JSException'), so they can be handled with the
32-
standard "Control.Exception" machinery.
33-
* interruptible: The import is asynchronous. The calling Haskell thread
34-
sleeps until the foreign code calls the `$c` JavaScript function with
35-
the result. The thread is in interruptible state while blocked, so it
36-
can receive asynchronous exceptions.
37-
38-
Unlike the FFI for native code, it's safe to call back into Haskell
39-
(`h$run`, `h$runSync`) from foreign code in any of the safety levels.
40-
Since JavaScript is single threaded, no Haskell threads can run while
41-
the foreign code is running.
42-
-}
43-
44-
module GHCJS.Foreign.Internal ( JSType(..)
5+
module GHCJS.Foreign.Internal ( JSTypeOf(..)
456
, jsTypeOf
46-
, JSONType(..)
7+
, JSONTypeOf(..)
478
, jsonTypeOf
489
-- , mvarRef
4910
, isTruthy
@@ -108,25 +69,25 @@ import qualified Data.Text.Lazy as TL (Text, toStrict, fromStrict)
10869
import Unsafe.Coerce
10970

11071
-- types returned by JS typeof operator
111-
data JSType = Undefined
112-
| Object
113-
| Boolean
114-
| Number
115-
| String
116-
| Symbol
117-
| Function
118-
| Other -- ^ implementation dependent
119-
deriving (Show, Eq, Ord, Enum, Typeable)
72+
data JSTypeOf = Undefined
73+
| Object
74+
| Boolean
75+
| Number
76+
| String
77+
| Symbol
78+
| Function
79+
| Other -- ^ implementation dependent
80+
deriving (Show, Eq, Ord, Enum, Typeable)
12081

12182
-- JSON value type
122-
data JSONType = JSONNull
123-
| JSONInteger
124-
| JSONFloat
125-
| JSONBool
126-
| JSONString
127-
| JSONArray
128-
| JSONObject
129-
deriving (Show, Eq, Ord, Enum, Typeable)
83+
data JSONTypeOf = JSONNull
84+
| JSONInteger
85+
| JSONFloat
86+
| JSONBool
87+
| JSONString
88+
| JSONArray
89+
| JSONObject
90+
deriving (Show, Eq, Ord, Enum, Typeable)
13091

13192
fromJSBool :: JSVal -> Bool
13293
fromJSBool b = js_fromBool b
@@ -257,11 +218,11 @@ listProps :: JSVal a -> IO [JSString]
257218
listProps o = fmap unsafeCoerce . Prim.fromJSArray =<< js_listProps o
258219
{-# INLINE listProps #-}
259220
-}
260-
jsTypeOf :: JSVal -> JSType
221+
jsTypeOf :: JSVal -> JSTypeOf
261222
jsTypeOf r = tagToEnum# (js_jsTypeOf r)
262223
{-# INLINE jsTypeOf #-}
263224

264-
jsonTypeOf :: JSVal -> JSONType
225+
jsonTypeOf :: JSVal -> JSONTypeOf
265226
jsonTypeOf r = tagToEnum# (js_jsonTypeOf r)
266227
{-# INLINE jsonTypeOf #-}
267228

GHCJS/Internal/Types.hs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,36 @@ import GHCJS.Prim (JSVal)
1818
instance NFData JSVal where
1919
rnf x = x `seq` ()
2020

21+
instance IsJSVal JSVal
22+
23+
-- | Instances of this class should be newtype wrappers around 'JSVal`. It
24+
-- should never be necessary to provide definitions for the methods, as
25+
-- they have defaults in terms of 'Coercible'. This is why the methods
26+
-- aren't exported by "GHCJS.Types".
2127
class IsJSVal a where
2228
jsval_ :: a -> JSVal
23-
2429
default jsval_ :: Coercible a JSVal => a -> JSVal
2530
jsval_ = coerce
2631
{-# INLINE jsval_ #-}
2732

33+
uncheckedWrapJSVal_ :: JSVal -> a
34+
default uncheckedWrapJSVal_ :: Coercible a JSVal => JSVal -> a
35+
uncheckedWrapJSVal_ = coerce
36+
{-# INLINE uncheckedWrapJSVal_ #-}
37+
38+
-- | This gets the 'JSVal' stored within a newtype wrapper.
2839
jsval :: IsJSVal a => a -> JSVal
2940
jsval = jsval_
3041
{-# INLINE jsval #-}
3142

43+
-- | This is an unchecked downcast from 'JSVal' to some newtype wrapper.
44+
-- Use with care, because this is an unchecked downcast. It should only
45+
-- be used when you know that the 'JSVal' is a valid inhabitant of the
46+
-- newtype.
47+
uncheckedWrapJSVal :: IsJSVal a => JSVal -> a
48+
uncheckedWrapJSVal = uncheckedWrapJSVal_
49+
{-# INLINE uncheckedWrapJSVal #-}
50+
3251
data MutabilityType s = Mutable
3352
| Immutable
3453
| STMutable s

0 commit comments

Comments
 (0)