codedownio / aeson-typescript

Generate TypeScript definition files from your ADTs
BSD 3-Clause "New" or "Revised" License
59 stars 27 forks source link

Deriving TypeScript fails for a higher-kinded, indirectly-recursive type #29

Closed langston-barrett closed 2 years ago

langston-barrett commented 2 years ago

I'm having difficulty deriving TypeScript instances for a higher-kinded, indirectly recursive type. I think this should work, given that Aeson happily generates JSON instances for the same type. This is with aeson-typescript version 0.4.0.0. Here's a minimal reproduction:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}

module Test () where

import qualified Data.Aeson as Aeson
import           Data.Functor.Identity
import           GHC.Generics (Generic)
import qualified Data.Aeson.TypeScript.TH as TS

data Foo a = Foo (Identity (Foo a))
  deriving (Generic)

$(TS.deriveTypeScript Aeson.defaultOptions ''Foo)

GHC gives me the following error:

Test.hs:20:3-48: error:
    • Could not deduce (TS.TypeScript (Identity (Foo TS.T)))
        arising from a use of ‘TS.getTypeScriptType’
      from the context: (TS.TypeScript (Identity (Foo a)),
                         TS.TypeScript a)
        bound by the instance declaration
        at Test.hs:20:3-48
    • In the expression:
        TS.getTypeScriptType
          (Data.Proxy.Proxy :: Data.Proxy.Proxy (Identity (Foo TS.T)))
      In the third argument of ‘aeson-typescript-0.4.0.0:Data.Aeson.TypeScript.Types.TSTypeAlternatives’, 
namely
        ‘[TS.getTypeScriptType
            (Data.Proxy.Proxy :: Data.Proxy.Proxy (Identity (Foo TS.T)))]’
      In the expression:
        ((aeson-typescript-0.4.0.0:Data.Aeson.TypeScript.Types.TSTypeAlternatives
            "IFoo")
           [(TS.getTypeScriptType (Data.Proxy.Proxy :: Data.Proxy.Proxy TS.T)
               <> "")])
          [TS.getTypeScriptType
             (Data.Proxy.Proxy :: Data.Proxy.Proxy (Identity (Foo TS.T)))]
   |
20 | $(TS.deriveTypeScript Aeson.defaultOptions ''Foo)
   |   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Splices generated with `-ddump-splices` ```haskell Test.hs:18:3-48: Splicing declarations TS.deriveTypeScript Aeson.defaultOptions ''Foo ======> instance (TS.TypeScript (Identity (Foo a_akjJ)), TS.TypeScript (a_akjJ :: ghc-prim-0.6.1:GHC.Types.Type)) => TS.TypeScript (Foo (a_akjJ :: ghc-prim-0.6.1:GHC.Types.Type)) where TS.getTypeScriptType _ = ("Foo" <> ("<" <> ((base-4.14.3.0:Data.OldList.intercalate ", ") [TS.getTypeScriptType (Data.Proxy.Proxy :: Data.Proxy.Proxy a_akjJ)] <> ">"))) TS.getTypeScriptDeclarations _ = (((aeson-typescript-0.4.0.0:Data.Aeson.TypeScript.Types.TSTypeAlternatives "Foo") [(TS.getTypeScriptType (Data.Proxy.Proxy :: Data.Proxy.Proxy TS.T) <> "")]) [("IFoo" <> (let vars_aklh = [(TS.getTypeScriptType (Data.Proxy.Proxy :: Data.Proxy.Proxy TS.T) <> "")] in ("<" <> ((base-4.14.3.0:Data.OldList.intercalate ", ") vars_aklh <> ">"))))] : [((aeson-typescript-0.4.0.0:Data.Aeson.TypeScript.Types.TSTypeAlternatives "IFoo") [(TS.getTypeScriptType (Data.Proxy.Proxy :: Data.Proxy.Proxy TS.T) <> "")]) [TS.getTypeScriptType (Data.Proxy.Proxy :: Data.Proxy.Proxy (Identity (Foo TS.T)))]]) TS.getParentTypes _ = [TS.TSType (Data.Proxy.Proxy :: Data.Proxy.Proxy (Identity (Foo a_akjJ)))] ```

The problem doesn't occur if you leave out the Identity. Replacing Identity with [] yields only a warning (this may have to do with the fact that [] has an instance in Data.Aeson.TypeScript.Instances).

thomasjm commented 2 years ago

Well, like the error says, it doesn't have an instance for TypeScript (Identity a). I added one and it works:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}

module Test () where

import qualified Data.Aeson as Aeson
import qualified Data.Aeson.TypeScript.TH as TS
import           Data.Functor.Identity
import           Data.Proxy
import           Data.Typeable
import           GHC.Generics (Generic)

data Foo a = Foo (Identity (Foo a))
  deriving (Generic)

instance (Typeable a, TS.TypeScript a) => TS.TypeScript (Identity a) where
  getTypeScriptType _ = TS.getTypeScriptType (Proxy @a)

$(TS.deriveTypeScript Aeson.defaultOptions ''Foo)

main = do
  putStrLn (TS.formatTSDeclarations (TS.getTypeScriptDeclarations @(Foo TS.T) Proxy))
langston-barrett commented 2 years ago

I see, thank you! It wasn't clear to me from the message Could not deduce (TypeScript (Identity (Foo T))) that what was needed was an instance TypeScript (Identity a), the presence of T threw me off.