packages feed

aeson-flowtyped-0.14.0: src/Data/Aeson/Flow.hs

{-# 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 <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 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