{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE InstanceSigs #-} {-# 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 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 qualified Data.Aeson.Key as AK import qualified Data.Aeson.KeyMap as AKM 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 a -- ^ omitable when null or undefined | 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 | Bottom -- ^ uninhabited type; @never@ in typescript, and @empty@ in flow | Mixed -- ^ @unknown@ in typescript, @mixed@ in flow | 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 (AK.toText name) PP.<+> PP.colon PP.<+> ppJson fty PP.<> PP.space ) (AKM.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 $ \e -> case e of 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 catMaybes . map (\ex -> case ex of 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 -> Int -- ^ total number of record or plain constructors -> Int -- ^ number of record constructors -> Int -- ^ number of plain constructors -> Bool -- ^ whether every constructor is nullary -> 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 p = Just . cleanup . T.pack . symbolVal . pGetName . fmap from $ p 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|] ]) deriveEq1 ''FlowTypeF