aeson-flowtyped-0.13.2.1: src/Data/Aeson/Flow.hs
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeInType #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
-- | Derive <https://flow.org/ Flow types> using aeson 'Options'.
--
-- Does not currently support the 'unwrapUnaryRecords' option.
module Data.Aeson.Flow
( -- * AST types
FlowTyped (..),
callType,
FlowTypeF,
FlowType,
-- , Fix (..)
pattern FObject,
pattern FExactObject,
pattern FObjectMap,
pattern FArray,
pattern FTuple,
pattern FLabelledTuple,
pattern FFun,
pattern FAlt,
pattern FPrim,
pattern FPrimBoolean,
pattern FPrimNumber,
pattern FPrimString,
pattern FPrimBottom,
pattern FPrimMixed,
pattern FPrimUnknown,
pattern FPrimNull,
pattern FPrimNever,
pattern FPrimUndefined,
pattern FPrimAny,
pattern FNullable,
pattern FOmitable,
pattern FLiteral,
pattern FTag,
pattern FName,
pattern FGenericParam,
pattern FCallType,
-- * Code generation
-- ** Wholesale ES6/flow/typescript modules
Export,
export,
RenderMode (..),
RenderOptions (..),
ModuleOptions (..),
typeScriptModuleOptions,
flowModuleOptions,
generateModule,
writeModule,
showTypeAs,
exportTypeAs,
-- ** Convenience for generating flowtypes from other types
FlowTyFields (..),
FlowDeconstructField,
-- ** TS specific
showTypeScriptType,
-- ** Flow specific
showFlowType,
-- * Dependencies
exportsDependencies,
dependencies,
-- * Utility
FlowName (..),
Flowable (..),
defaultFlowTypeName,
defaultFlowType,
)
where
import Control.Monad.Reader
import Control.Monad.State.Strict
import qualified Data.Aeson as A
import Data.Aeson.Types
( Options (..),
SumEncoding (..),
)
import Data.Eq.Deriving (deriveEq1)
import Data.Fix (Fix (..))
import Data.Fixed (Fixed)
import Data.Functor.Classes
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as H
import qualified Data.HashSet as HashSet
import Data.Int
import qualified Data.IntMap.Strict as I
import qualified Data.IntSet as IntSet
import qualified Data.Map.Strict as M
import Data.Maybe
import qualified Data.Monoid as Monoid
import Data.Proxy
import Data.Reflection
import Data.Scientific (Scientific)
import qualified Data.Set as Set
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import qualified Data.Text.Lazy as TL
import Data.Time (UTCTime)
import qualified Data.Tree as Tree
import Data.Typeable
import Data.Vector (Vector)
import qualified Data.Vector as V
import qualified Data.Vector.Storable as VS
import qualified Data.Vector.Unboxed as VU
import qualified Data.Void as Void
import Data.Word
import GHC.Generics
( D1,
Generic,
Meta (..),
Rep,
from,
)
import GHC.TypeLits
import qualified Generics.SOP as SOP
import qualified Generics.SOP.GGP as SOP
import qualified Text.PrettyPrint.Leijen as PP
-- | The main AST for flowtypes.
data FlowTypeF a
= Object !(HashMap Text a)
| ExactObject !(HashMap Text a)
| ObjectMap !Text a a
| Array a
| Tuple !(Vector a)
| LabelledTuple !(Vector (Maybe Text, a))
| Fun !(Vector (Text, a)) a
| Alt a a
| Prim !PrimType
| Nullable a
| -- | omitable when null or undefined
Omitable a
| Literal !A.Value
| Tag !Text
| GenericParam !Int
| CallType !FlowName [a]
| SomeFlowType !Flowable
| TypeDoc !(Vector Text) a
deriving (Show, Eq, Functor, Traversable, Foldable)
-- | A primitive flow/javascript type
data PrimType
= Boolean
| Number
| String
| Null
| Undefined
| -- | uninhabited type; @never@ in typescript, and @empty@ in flow
Bottom
| -- | @unknown@ in typescript, @mixed@ in flow
Mixed
| Any
deriving (Show, Read, Eq, Ord)
-- | A name for a flowtyped data-type. These are returned by 'dependencies'.
data FlowName where
FlowName :: (FlowTyped a) => Proxy a -> Text -> FlowName
data Flowable where
Flowable :: (FlowTyped a) => Proxy a -> Flowable
data Showy f a
= forall s.
Reifies s (Int -> a -> ShowS) =>
Showy
(f (Inj s a))
instance Show1 (Showy FlowTypeF) where
liftShowsPrec _ _ i (Showy a) = showsPrec i a
--------------------------------------------------------------------------------
-- Magical newtype for injecting showsPrec into any arbitrary Show
inj :: Proxy s -> a -> Inj s a
inj _ = Inj
newtype Inj s a = Inj a
-- needs UndecidableInstances
instance Reifies s (Int -> a -> ShowS) => Show (Inj s a) where
showsPrec i (Inj a) = reflect (Proxy :: Proxy s) i a
--------------------------------------------------------------------------------
data RenderMode = RenderTypeScript | RenderFlow
deriving (Eq, Show)
data RenderOptions = RenderOptions
{ renderMode :: !RenderMode
}
deriving (Eq, Show)
instance Show FlowName where
show (FlowName _ t) = show t
instance Eq FlowName where
FlowName _t0 n0 == FlowName _t1 n1 = n0 == n1
-- case eqT :: Maybe (t0 :~: t1) of
-- Just Refl -> (t0, n0) == (t1, n1)
-- Nothing -> False
instance Ord FlowName where
FlowName _t0 n0 `compare` FlowName _t1 n1 = n0 `compare` n1
-- XXX this breaks using (typeRep t0, n0) `compare` (typeRep t1, n1) for some
-- reason... dunno why
instance Show Flowable where
show (Flowable t) = show (typeRep t)
instance Eq Flowable where
Flowable a == Flowable b = typeRep a == typeRep b
instance Ord Flowable where
Flowable a `compare` Flowable b = typeRep a `compare` typeRep b
-- XXX: vector >= 0.12 has Eq1 vector which allows us to use eq for Fix
-- FlowTypeF and related types
--------------------------------------------------------------------------------
pattern FObject :: HashMap Text FlowType -> FlowType
pattern FObject x = Fix (Object x)
pattern FExactObject :: HashMap Text FlowType -> FlowType
pattern FExactObject x = Fix (ExactObject x)
pattern FObjectMap :: Text -> FlowType -> FlowType -> FlowType
pattern FObjectMap keyName keyType vals = Fix (ObjectMap keyName keyType vals)
pattern FArray :: FlowType -> FlowType
pattern FArray a = Fix (Array a)
pattern FTuple :: Vector FlowType -> FlowType
pattern FTuple a = Fix (Tuple a)
pattern FLabelledTuple :: Vector (Maybe Text, FlowType) -> FlowType
pattern FLabelledTuple a = Fix (LabelledTuple a)
pattern FFun :: Vector (Text, FlowType) -> FlowType -> FlowType
pattern FFun v t = Fix (Fun v t)
pattern FAlt :: FlowType -> FlowType -> FlowType
pattern FAlt a b = Fix (Alt a b)
pattern FPrim :: PrimType -> FlowType
pattern FPrim a = Fix (Prim a)
pattern FPrimBoolean :: FlowType
pattern FPrimBoolean = FPrim Boolean
pattern FPrimNumber :: FlowType
pattern FPrimNumber = FPrim Number
pattern FPrimString :: FlowType
pattern FPrimString = FPrim String
pattern FPrimBottom :: FlowType
pattern FPrimBottom = FPrim Bottom
pattern FPrimMixed :: FlowType
pattern FPrimMixed = FPrim Mixed
pattern FPrimUnknown :: FlowType
pattern FPrimUnknown = FPrim Mixed
pattern FPrimAny :: FlowType
pattern FPrimAny = FPrim Any
pattern FPrimNever :: FlowType
pattern FPrimNever = FPrim Bottom
pattern FPrimNull :: FlowType
pattern FPrimNull = FPrim Null
pattern FPrimUndefined :: FlowType
pattern FPrimUndefined = FPrim Undefined
pattern FNullable :: FlowType -> FlowType
pattern FNullable a = Fix (Nullable a)
pattern FOmitable :: FlowType -> FlowType
pattern FOmitable a = Fix (Omitable a)
pattern FLiteral :: A.Value -> FlowType
pattern FLiteral a = Fix (Literal a)
pattern FTag :: Text -> FlowType
pattern FTag a = Fix (Tag a)
pattern FName :: FlowName -> FlowType
pattern FName a = Fix (CallType a [])
pattern FGenericParam :: Int -> FlowType
pattern FGenericParam a = Fix (GenericParam a)
pattern FCallType :: FlowName -> [FlowType] -> FlowType
pattern FCallType f xs = Fix (CallType f xs)
pattern FTypeDoc :: Vector Text -> FlowType -> FlowType
pattern FTypeDoc f xs = Fix (TypeDoc f xs)
--------------------------------------------------------------------------------
instance Show1 FlowTypeF where
liftShowsPrec sp sl i a =
liftShowsPrec sp sl i (reify sp (\p -> Showy (fmap (inj p) a)))
type FlowType = Fix FlowTypeF
text :: Text -> PP.Doc
text = PP.text . T.unpack
squotes :: Text -> PP.Doc
squotes = PP.squotes . text . T.replace "'" "\\'"
type Poly = ReaderT RenderOptions (Reader [Flowable])
ppAlts :: [FlowType] -> FlowType -> Poly PP.Doc
ppAlts alts (Fix f) = case f of
Alt a b -> ppAlts (a : alts) b
x -> PP.align . sep <$> mapM pp (reverse (Fix x : alts))
where
sep [x] = x
sep (x : xs) = x PP.<+> PP.string "|" PP.<$> sep xs
sep _ = PP.empty
braceList :: [PP.Doc] -> PP.Doc
braceList =
(\s -> PP.lbrace PP.</> s PP.</> PP.rbrace)
. PP.align
. PP.sep
. PP.punctuate PP.comma
braceBarList :: [PP.Doc] -> PP.Doc
braceBarList =
(\s -> PP.text "{|" PP.</> s PP.</> PP.text "|}")
. PP.align
. PP.sep
. PP.punctuate PP.comma
ppJson :: A.Value -> PP.Doc
ppJson v = case v of
A.Array a -> PP.list (map ppJson (V.toList a))
A.String t -> squotes t
A.Number n -> PP.string (show n)
A.Bool t -> if t then PP.string "true" else PP.string "false"
A.Null -> PP.string "null"
A.Object obj ->
braceBarList
( map
( \(name, fty) ->
PP.space
PP.<> text name
PP.<+> PP.colon
PP.<+> ppJson fty
PP.<> PP.space
)
(H.toList obj)
)
mayWrap :: FlowType -> PP.Doc -> PP.Doc
mayWrap (Fix f) x = case f of
Nullable _ -> PP.parens x
Omitable _ -> PP.parens x
Alt _ _ -> PP.parens x
Array _ -> PP.parens x
_ -> x
ppObject :: HashMap Text FlowType -> Poly [PP.Doc]
ppObject = mapM ppField . H.toList
where
ppField (name, fty) = do
case fty of
Fix (Omitable fty') ->
-- key?: type
(\fty'' -> text name PP.<> PP.text "?" PP.<> PP.colon PP.<+> fty'')
<$> pp fty'
fty' ->
-- key: type
(\fty'' -> text name PP.<> PP.colon PP.<+> fty'') <$> pp fty'
polyVarNames :: [Text]
polyVarNames =
map T.singleton ['A' .. 'Z']
++ zipWith (\i t -> t `T.append` T.pack (show i)) [0 :: Int ..] polyVarNames
pp :: FlowType -> Poly PP.Doc
pp (Fix ft) = case ft of
ObjectMap keyName keyType a -> do
keyTy <- pp keyType
r <- pp a
pure
( braceList
[ PP.brackets (text keyName PP.<> PP.text ":" PP.<+> keyTy)
PP.<> PP.colon
PP.<+> r
]
)
Object hm -> braceList <$> ppObject hm
ExactObject hm -> do
mode <- asks renderMode
case mode of
RenderFlow -> braceBarList <$> ppObject hm
RenderTypeScript -> braceList <$> ppObject hm
-- x[]
Array a -> (\r -> mayWrap a r PP.<> PP.string "[]") <$> pp a
-- [x, y, z]
Tuple t -> PP.list <$> mapM pp (V.toList t)
-- [l1: x, y, l2: z]
LabelledTuple t ->
PP.list
<$> mapM
( \(mlbl, ty) -> case mlbl of
Just lbl -> ((text lbl PP.<> PP.string ":") PP.<+>) <$> pp ty
Nothing -> pp ty
)
(V.toList t)
Alt a b -> ppAlts [a] b
Prim pt -> do
mode <- asks renderMode
return $ case pt of
Boolean -> PP.text "boolean"
Number -> PP.text "number"
String -> PP.text "string"
Null -> PP.text "null"
Undefined -> PP.text "undefined"
Any -> PP.text "any"
Mixed -> case mode of
RenderFlow -> PP.text "mixed"
RenderTypeScript -> PP.text "unknown"
Bottom -> case mode of
RenderFlow -> PP.text "empty"
RenderTypeScript -> PP.text "never"
Nullable a ->
-- n.b. there is no 'undefined' in json. void is undefined | null in both ts
-- and flow (and ?x syntax for void|x)
(\a' -> PP.text "null" PP.<+> PP.string "|" PP.<+> a') <$> pp a
Omitable a -> pp (FNullable a)
Literal a -> return (ppJson a)
Tag t -> return (squotes t)
GenericParam ix -> return (text (polyVarNames !! ix))
CallType (FlowName _ t) [] -> return (text t)
CallType (FlowName _ t) args -> do
vs <- mapM pp args
return (text t PP.<> PP.angles (PP.hsep (PP.punctuate PP.comma vs)))
TypeDoc _doc t -> pp t
_ -> return (PP.string (show ft))
-- | Pretty-print a flowtype in flowtype syntax
renderTypeWithOptions :: RenderOptions -> FlowType -> [Flowable] -> PP.Doc
renderTypeWithOptions opts ft params =
(pp ft `runReaderT` opts) `runReader` params
-- | Pretty-print a flowtype in flowtype syntax
showFlowType :: FlowType -> [Flowable] -> Text
showFlowType ft params =
T.pack . show $
renderTypeWithOptions
RenderOptions {renderMode = RenderFlow}
ft
params
-- | Pretty-print a flowtype in flowtype syntax
showTypeScriptType :: FlowType -> [Flowable] -> Text
showTypeScriptType ft params =
T.pack . show $
renderTypeWithOptions
RenderOptions {renderMode = RenderTypeScript}
ft
params
--------------------------------------------------------------------------------
-- Module exporting
-- | Generate a @ export type @ declaration.
exportTypeAs :: RenderOptions -> Text -> FlowType -> [Flowable] -> Text
exportTypeAs opts = showTypeAs opts True
-- | Generate a @ type @ declaration, possibly an export.
showTypeAs :: RenderOptions -> Bool -> Text -> FlowType -> [Flowable] -> Text
showTypeAs opts isExport name ft params =
T.pack
. render
$ PP.string (if isExport then "export type " else "type ")
PP.<> text name
PP.<> renderedParams
PP.<+> text "="
PP.<+> renderedTypeDecl
PP.<> text ";"
PP.<> PP.linebreak
where
renderedTypeDecl = renderTypeWithOptions opts ft params
renderedParams
| null params = mempty
| otherwise =
PP.angles
( PP.hsep
(PP.punctuate PP.comma (map text (take (length params) polyVarNames)))
)
render = ($ []) . PP.displayS . PP.renderPretty 1.0 80
-- | Compute all the dependencies of a 'FlowTyped' thing, including itself.
dependencies :: (FlowTyped a) => Proxy a -> Set.Set FlowName
dependencies p0 =
( case flowTypeName p0 of
Just t -> Set.insert (FlowName p0 t)
Nothing -> id
)
(M.foldl' Set.union Set.empty (transitiveDeps (Flowable p0) M.empty))
where
flowNameToFlowable (FlowName fn _) = Flowable fn
immediateDeps :: FlowType -> Set.Set FlowName
immediateDeps (FCallType n tys) =
Set.insert n (Set.unions (map immediateDeps tys))
immediateDeps (Fix p) = foldMap immediateDeps p
transitiveDeps ::
Flowable ->
M.Map Flowable (Set.Set FlowName) ->
M.Map Flowable (Set.Set FlowName)
transitiveDeps fpf@(Flowable p) acc
| fpf `M.notMember` acc =
let imms = immediateDeps (flowType p)
withThis = M.insert fpf imms acc
in Set.foldr' (transitiveDeps . flowNameToFlowable) withThis imms
| otherwise =
acc
data ModuleOptions = ModuleOptions
{ -- | You might want to change this to include e.g. flow-runtime
pragmas :: [Text],
header :: [Text],
exportDeps :: Bool,
computeDeps :: Bool,
renderOptions :: RenderOptions
}
deriving (Eq, Show)
flowModuleOptions :: ModuleOptions
flowModuleOptions =
ModuleOptions
{ pragmas = ["// @flow"],
header = ["This module has been generated by aeson-flowtyped."],
exportDeps = True,
computeDeps = True,
renderOptions = RenderOptions {renderMode = RenderFlow}
}
typeScriptModuleOptions :: ModuleOptions
typeScriptModuleOptions =
ModuleOptions
{ pragmas = [],
header = ["This module has been generated by aeson-flowtyped."],
exportDeps = True,
computeDeps = True,
renderOptions = RenderOptions {renderMode = RenderTypeScript}
}
data Export where
Export :: FlowTyped a => Proxy a -> Export
export :: forall a. FlowTyped a => Export
export = Export (Proxy :: Proxy a)
instance Eq Export where
Export p0 == Export p1 =
flowTypeName p0 == flowTypeName p1 || typeRep p0 == typeRep p1
exportsDependencies :: [Export] -> Set.Set FlowName
exportsDependencies = foldMap (\(Export a) -> dependencies a)
generateModule :: ModuleOptions -> [Export] -> Text
generateModule opts exports =
T.unlines $
( \m ->
(pragmas opts ++ map ("// " `T.append`) (header opts)) ++ (T.empty : m)
)
. map flowDecl
. flowNames
$ exports
where
flowNames =
if computeDeps opts
then Set.toList . exportsDependencies
else mapMaybe (\(Export p) -> FlowName p <$> flowTypeName p)
flowDecl (FlowName p name) =
if Export p `elem` exports || exportDeps opts
then showTypeAs (renderOptions opts) True name (flowType p) (flowTypeVars p)
else
showTypeAs
(renderOptions opts)
False
name
(flowType p)
(flowTypeVars p)
writeModule :: ModuleOptions -> FilePath -> [Export] -> IO ()
writeModule opts path = TIO.writeFile path . generateModule opts
--------------------------------------------------------------------------------
type family FlowDeconstructField (k :: t) :: (Symbol, *)
type instance FlowDeconstructField '(a, b) = '(a, b)
-- | Useful for declaring flowtypes from type-level key/value sets, like
--
-- @
-- FlowTyFields :: FlowTyFields Person '['("name", String), '("email", String)]
-- @
data FlowTyFields :: * -> [k] -> * where
FlowTyFields :: FlowTyFields k fs
class ReifyFlowTyFields a where
reifyFlowTyFields :: Proxy a -> HashMap Text FlowType -> HashMap Text FlowType
instance ReifyFlowTyFields '[] where
reifyFlowTyFields _ = id
instance
( FlowDeconstructField x ~ '(k, v),
KnownSymbol k,
FlowTyped v,
ReifyFlowTyFields xs
) =>
ReifyFlowTyFields (x : xs)
where
reifyFlowTyFields _ acc =
reifyFlowTyFields (Proxy :: Proxy xs)
$! H.insert
(T.pack (symbolVal (Proxy :: Proxy k)))
(flowType (Proxy :: Proxy v))
acc
instance (FlowTyped a, ReifyFlowTyFields (fs :: [k]), Typeable fs, Typeable k) => FlowTyped (FlowTyFields a fs) where
flowType _ = FExactObject (reifyFlowTyFields (Proxy :: Proxy fs) H.empty)
flowTypeName _ = flowTypeName (Proxy :: Proxy a)
--------------------------------------------------------------------------------
callType' :: (FlowTyped a) => Proxy a -> [FlowType] -> FlowType
callType' p args = case flowTypeName p of
Just n -> FCallType (FlowName p n) args
Nothing -> flowType p
callType :: forall a. FlowTyped a => Proxy a -> FlowType
callType p = callType' p (map (\(Flowable t) -> callType t) (flowTypeVars p))
class Typeable a => FlowTyped a where
flowType :: Proxy a -> FlowType
flowTypeName :: Proxy a -> Maybe Text
flowTypeVars :: Proxy a -> [Flowable]
flowTypeVars _ = []
flowOptions :: Proxy a -> Options
flowOptions _ = A.defaultOptions
isPrim :: Proxy a -> Bool
isPrim _ = False
default flowType ::
(SOP.GDatatypeInfo a, SOP.All2 FlowTyped (SOP.GCode a)) =>
Proxy a ->
FlowType
flowType p = flowTypeFromSOP (flowOptions p) (SOP.gdatatypeInfo p)
default flowTypeName ::
(Generic a, Rep a ~ D1 ('MetaData name mod pkg t) c, KnownSymbol name) =>
Proxy a ->
Maybe Text
flowTypeName = defaultFlowTypeName
-- | 'flowType' using 'SOP.HasDatatypeInfo'
defaultFlowType ::
(SOP.HasDatatypeInfo a, SOP.All2 FlowTyped (SOP.Code a)) =>
Options ->
Proxy a ->
FlowType
defaultFlowType opts p = flowTypeFromSOP opts (SOP.datatypeInfo p)
flowTypeFromSOP ::
SOP.All2 FlowTyped ty => Options -> SOP.DatatypeInfo ty -> FlowType
flowTypeFromSOP opts di = case comments of
[] -> ft
_ -> FTypeDoc (V.fromList comments) ft
where
(ft, comments) =
( case di of
SOP.ADT moduleName typeName constrInfos _strictness -> do
modify' (moduleComment moduleName :)
modify' (typeComment typeName :)
pure . foldr1 FAlt $! case constrsKind constrInfos 0 0 0 True of
SumRecords -> sumEncode constrInfos
SumConstructors -> sumEncode constrInfos
SumNullaryConstructors -> sumNullaryEncode constrInfos
SingleRecord -> singleEncode constrInfos
SingleConstructor -> singleEncode constrInfos
SingleNullaryConstructor -> [FTuple V.empty]
Unsupported ->
error $ "aeson-flowtyped: Unsupported type " ++ show typeName
SOP.Newtype moduleName typeName constrInfo -> do
modify' (moduleComment moduleName :)
modify' (typeComment typeName :)
case constrInfo of
(SOP.Constructor constrName :: SOP.ConstructorInfo '[x]) -> do
modify' (constrComment constrName :)
pure (callType (Proxy :: Proxy x))
SOP.Record constrName ((SOP.FieldInfo _fname :: SOP.FieldInfo x) SOP.:* SOP.Nil) ->
do
modify' (constrComment constrName :)
pure (callType (Proxy :: Proxy x))
)
`runState` []
constrsKind ::
SOP.NP SOP.ConstructorInfo ty ->
-- | total number of record or plain constructors
Int ->
-- | number of record constructors
Int ->
-- | number of plain constructors
Int ->
-- | whether every constructor is nullary
Bool ->
ConstructorsKind
constrsKind SOP.Nil !total !recs !plains !allNullary
| recs == 1 && plains == 0 = SingleRecord
| plains == 1 && recs == 0 =
if allNullary
then SingleNullaryConstructor
else SingleConstructor
| recs == total && plains == 0 = SumRecords
| plains == total && recs == 0 =
if allNullary
then SumNullaryConstructors
else SumConstructors
| otherwise = Unsupported
constrsKind (constr SOP.:* rest) total recs plains allNullary =
case constr of
(SOP.Constructor {} :: SOP.ConstructorInfo flds) ->
constrsKind
rest
(total + 1)
recs
(plains + 1)
(allNullary && isNullary @flds)
(SOP.Record {} :: SOP.ConstructorInfo flds) ->
constrsKind
rest
(total + 1)
(recs + 1)
plains
(allNullary && isNullary @flds)
_ -> Unsupported
sumEncode,
singleEncode,
sumNullaryEncode ::
SOP.All2 FlowTyped ty => SOP.NP SOP.ConstructorInfo ty -> [FlowType]
sumEncode constrsNP =
SOP.hcfoldMap
(Proxy :: Proxy (SOP.All FlowTyped))
( \case
(SOP.Constructor constrName :: SOP.ConstructorInfo xs) ->
let value =
let tuple =
V.fromList
$! SOP.hcfoldMap
(Proxy :: Proxy FlowTyped)
(\(Proxy :: SOP.Proxy x) -> [callType (Proxy :: Proxy x)])
(SOP.hpure Proxy :: SOP.NP Proxy xs)
in case V.length tuple of
1 -> V.head tuple
_ -> FTuple tuple
hasContents =
Monoid.getAny
$! SOP.hcfoldMap
(Proxy :: Proxy SOP.Top)
(\_ -> Monoid.Any True)
(SOP.hpure Proxy :: SOP.NP Proxy xs)
in case sumEncoding opts of
TaggedObject (T.pack -> tagFld) contentsFld
| hasContents ->
[ FExactObject
( H.fromList
[ (tagFld, renderConstrTag constrName),
(T.pack contentsFld, value)
]
)
]
| otherwise ->
[ FExactObject
(H.singleton tagFld (renderConstrTag constrName))
]
UntaggedValue -> [value]
ObjectWithSingleField ->
[ FExactObject
( H.fromList
[(T.pack (constructorTagModifier opts constrName), value)]
)
]
TwoElemArray ->
[FTuple (V.fromListN 2 [renderConstrTag constrName, value])]
SOP.Record constrName flds ->
let fldsList :: H.HashMap Text FlowType
fldsList =
H.fromList
$! SOP.hcfoldMap
(Proxy :: Proxy FlowTyped)
( \(SOP.FieldInfo fname :: SOP.FieldInfo x) ->
[ ( T.pack (fieldLabelModifier opts fname),
callType (Proxy :: Proxy x)
)
]
)
flds
in case sumEncoding opts of
-- The contents field is not used here but the tag one is
TaggedObject (T.pack -> tagFld) _contentsFld ->
[ FExactObject
(H.insert tagFld (renderConstrTag constrName) fldsList)
]
UntaggedValue -> [FExactObject fldsList]
ObjectWithSingleField ->
[ FExactObject
( H.singleton
(T.pack (constructorTagModifier opts constrName))
(FExactObject fldsList)
)
]
TwoElemArray ->
[ FTuple
( V.fromListN
2
[renderConstrTag constrName, FExactObject fldsList]
)
]
SOP.Infix {} ->
error "aeson-flowtyped: Unsupported use of infix constructor"
)
constrsNP
singleEncode (constr SOP.:* SOP.Nil) = case constr of
(SOP.Constructor _constrName :: SOP.ConstructorInfo xs) ->
[ FTuple . V.fromList
$! SOP.hcfoldMap
(Proxy :: Proxy FlowTyped)
(\(Proxy :: SOP.Proxy x) -> [callType (Proxy :: Proxy x)])
(SOP.hpure Proxy :: SOP.NP Proxy xs)
]
SOP.Record _constrName flds ->
[ FExactObject
$! H.fromList
$! SOP.hcfoldMap
(Proxy :: Proxy FlowTyped)
( \(SOP.FieldInfo fname :: SOP.FieldInfo x) ->
[ ( T.pack (fieldLabelModifier opts fname),
callType (Proxy :: Proxy x)
)
]
)
flds
]
SOP.Infix {} ->
error "aeson-flowtyped: Unsupported use of infix constructor"
singleEncode _ =
error "aeson-flowtyped: Errorneous detection of single constructor"
sumNullaryEncode constrsNP
| allNullaryToStringTag opts =
[ FLiteral (A.String (T.pack (constructorTagModifier opts tag)))
| tag <-
SOP.hcfoldMap
(Proxy :: Proxy SOP.Top)
(\(SOP.Constructor constrName) -> [constrName])
constrsNP
]
| otherwise =
[ nullarySumObject (T.pack (constructorTagModifier opts tag))
| tag <-
SOP.hcfoldMap
(Proxy :: Proxy SOP.Top)
(\(SOP.Constructor constrName) -> [constrName])
constrsNP
]
nullarySumObject tagValue = case sumEncoding opts of
TaggedObject (T.pack -> tagFld) _contentsFld ->
FExactObject (H.singleton tagFld (FLiteral (A.String tagValue)))
UntaggedValue -> FTuple V.empty
ObjectWithSingleField ->
FExactObject (H.singleton tagValue (FTuple V.empty))
TwoElemArray ->
FTuple (V.fromListN 2 [FLiteral (A.String tagValue), FTuple V.empty])
renderConstrTag = FLiteral . A.String . T.pack . constructorTagModifier opts
moduleComment s = T.concat ["Origin module: `", T.pack s, "`"]
typeComment s = T.concat ["Origin type: ", T.pack s]
constrComment s = T.concat ["Origin constructor: ", T.pack s]
isNullary :: forall xs. SOP.SListI xs => Bool
isNullary = SOP.lengthSList (Proxy :: Proxy xs) == 0
data ConstructorsKind
= SumRecords
| SumConstructors
| SumNullaryConstructors
| SingleRecord
| SingleConstructor
| SingleNullaryConstructor
| Unsupported
-- | 'flowTypeName' using 'Generic'
defaultFlowTypeName ::
(Generic a, Rep a ~ D1 ('MetaData name mod pkg t) c, KnownSymbol name) =>
Proxy a ->
Maybe Text
defaultFlowTypeName =
Just . cleanup . T.pack . symbolVal . pGetName . fmap from
where
pGetName :: Proxy (D1 ('MetaData name mod pkg t) c x) -> Proxy name
pGetName _ = Proxy
cleanup = T.replace "'" "_" -- I think this is the only illegal token in JS
-- that's allowed in Haskell, other than type
-- operators... TODO, rename type operators
--------------------------------------------------------------------------------
-- Instances
instance (FlowTyped a) => FlowTyped [a] where
flowType _ = FArray (callType (Proxy :: Proxy a))
isPrim _ = True
flowTypeName _ = Nothing
instance (FlowTyped a) => FlowTyped (Vector a) where
flowType _ = FArray (callType (Proxy :: Proxy a))
isPrim _ = True
flowTypeName _ = Nothing
instance (FlowTyped a) => FlowTyped (VU.Vector a) where
flowType _ = FArray (callType (Proxy :: Proxy a))
isPrim _ = True
flowTypeName _ = Nothing
instance (FlowTyped a) => FlowTyped (VS.Vector a) where
flowType _ = FArray (callType (Proxy :: Proxy a))
isPrim _ = True
flowTypeName _ = Nothing
instance
( FlowTyped a,
FlowTyped b
) =>
FlowTyped (a, b)
where
flowTypeName _ = Nothing
flowType _ = FTuple (V.fromList [aFt, bFt])
where
aFt = callType (Proxy :: Proxy a)
bFt = callType (Proxy :: Proxy b)
instance (FlowTyped a) => FlowTyped (Maybe a) where
flowType _ = FNullable (callType (Proxy :: Proxy a))
isPrim _ = True
flowTypeName _ = Nothing
instance
( FlowTyped a,
FlowTyped b
) =>
FlowTyped (Either a b)
where
flowTypeName _ = Nothing
flowType _ =
FAlt
(FExactObject (H.fromList [("Left", aFt)]))
(FExactObject (H.fromList [("Right", bFt)]))
where
aFt = callType (Proxy :: Proxy a)
bFt = callType (Proxy :: Proxy b)
instance
( FlowTyped a,
FlowTyped b,
FlowTyped c
) =>
FlowTyped (a, b, c)
where
flowTypeName _ = Nothing
flowType _ = FTuple (V.fromList [aFt, bFt, cFt])
where
aFt = callType (Proxy :: Proxy a)
bFt = callType (Proxy :: Proxy b)
cFt = callType (Proxy :: Proxy c)
instance
( FlowTyped a,
FlowTyped b,
FlowTyped c,
FlowTyped d
) =>
FlowTyped (a, b, c, d)
where
flowTypeName _ = Nothing
flowType _ = FTuple (V.fromList [aFt, bFt, cFt, dFt])
where
aFt = callType (Proxy :: Proxy a)
bFt = callType (Proxy :: Proxy b)
cFt = callType (Proxy :: Proxy c)
dFt = callType (Proxy :: Proxy d)
instance
( FlowTyped a,
FlowTyped b,
FlowTyped c,
FlowTyped d,
FlowTyped e
) =>
FlowTyped (a, b, c, d, e)
where
flowTypeName _ = Nothing
flowType _ = FTuple (V.fromList [aFt, bFt, cFt, dFt, eFt])
where
aFt = callType (Proxy :: Proxy a)
bFt = callType (Proxy :: Proxy b)
cFt = callType (Proxy :: Proxy c)
dFt = callType (Proxy :: Proxy d)
eFt = callType (Proxy :: Proxy e)
instance FlowTyped Text where
isPrim _ = True
flowType _ = FPrimString
flowTypeName _ = Nothing
instance FlowTyped TL.Text where
isPrim _ = True
flowType _ = FPrimString
flowTypeName _ = Nothing
instance {-# OVERLAPS #-} FlowTyped String where
isPrim _ = True
flowType _ = FPrimString
flowTypeName _ = Nothing
instance FlowTyped Void.Void where
isPrim _ = True
flowType _ = FPrimBottom
flowTypeName _ = Nothing
instance FlowTyped Char where
isPrim _ = True
flowType _ = FPrimString
flowTypeName _ = Nothing
instance FlowTyped Bool where
isPrim _ = True
flowType _ = FPrimBoolean
flowTypeName _ = Nothing
instance FlowTyped A.Value where
isPrim _ = True
flowType _ = FPrimMixed
flowTypeName _ = Nothing
instance FlowTyped UTCTime where
isPrim _ = False
flowType _ = FPrimString
flowTypeName _ = Nothing
instance (Typeable (a :: k), Typeable k) => FlowTyped (Fixed a) where
isPrim _ = False
flowType _ = FPrimNumber
flowTypeName _ = Nothing
instance
( FlowTyped k,
FlowTyped a,
A.ToJSONKey k
) =>
FlowTyped (HashMap k a)
where
-- XXX this is getting quite incoherent, what makes something "Prim" or not...
isPrim _ = True
flowType _ = case A.toJSONKey :: A.ToJSONKeyFunction k of
A.ToJSONKeyText {} ->
FObjectMap "key" FPrimString (callType (Proxy :: Proxy a))
A.ToJSONKeyValue {} ->
FArray
( FTuple
( V.fromListN
2
[callType (Proxy :: Proxy k), callType (Proxy :: Proxy a)]
)
)
flowTypeName _ = Nothing
instance (FlowTyped a) => FlowTyped (Set.Set a) where
isPrim _ = False
flowType _ = FArray (callType (Proxy :: Proxy a))
flowTypeName _ = Nothing
instance FlowTyped IntSet.IntSet where
isPrim _ = False
flowType _ = FArray FPrimNumber -- (Fix (Prim Number))
flowTypeName _ = Nothing
instance (FlowTyped a) => FlowTyped (I.IntMap a) where
isPrim _ = False
flowType _ =
Fix
. Array
. Fix
. Tuple
. V.fromListN 2
$ [FPrimNumber, callType (Proxy :: Proxy a)]
flowTypeName _ = Nothing
instance (FlowTyped a) => FlowTyped (HashSet.HashSet a) where
isPrim _ = False
flowType _ = FArray (callType (Proxy :: Proxy a))
flowTypeName _ = Nothing
-- | This instance is defined recursively. You'll probably need to use
-- 'dependencies' to extract a usable definition
instance (FlowTyped a) => FlowTyped (Tree.Tree a) where
isPrim _ = False
flowType _ =
FTuple
( V.fromList
[ FGenericParam 0,
FArray (callType' (Proxy :: Proxy (Tree.Tree a)) [FGenericParam 0])
]
)
flowTypeName _ = Just "Tree"
flowTypeVars _ = [Flowable (Proxy :: Proxy a)]
instance FlowTyped () where
isPrim _ = False
flowType _ = FTuple V.empty
flowTypeName _ = Nothing
-- monomorphic numeric instances
$( concat
<$> mapM
( \ty ->
[d|
instance FlowTyped $ty where
isPrim _ = False
flowType _ = FPrimNumber
flowTypeName _ = Nothing
|]
)
[ [t|Int|],
[t|Int8|],
[t|Int16|],
[t|Int32|],
[t|Int64|],
[t|Word|],
[t|Word8|],
[t|Word16|],
[t|Word32|],
[t|Word64|],
[t|Float|],
[t|Double|],
[t|Scientific|],
[t|Integer|]
]
)
deriveEq1 ''FlowTypeF