packages feed

large-anon-0.1.0.0: test/Test/Sanity/TypeLevelMetadata.hs

{-# LANGUAGE DataKinds            #-}
{-# LANGUAGE FlexibleContexts     #-}
{-# LANGUAGE KindSignatures       #-}
{-# LANGUAGE OverloadedLabels     #-}
{-# LANGUAGE PolyKinds            #-}
{-# LANGUAGE ScopedTypeVariables  #-}
{-# LANGUAGE TypeApplications     #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE TypeOperators        #-}
{-# LANGUAGE UndecidableInstances #-}

{-# OPTIONS_GHC -fplugin=Data.Record.Anon.Plugin #-}

-- | Test type-level metadata
--
-- It is important that we have a separate test for this, because we don't
-- actually rely on this information anywhere; it is only for SOP interop.
module Test.Sanity.TypeLevelMetadata (tests) where

import Data.Record.Generic
import Data.Record.Generic.SOP
import Data.Kind
import Data.Typeable
import GHC.TypeLits

import Test.Tasty
import Test.Tasty.HUnit

import Data.Record.Anon

import qualified Data.Record.Anon.Simple   as S
import qualified Data.Record.Anon.Advanced as A

tests :: TestTree
tests = testGroup "Test.Sanity.TypeLevelMetadata" [
      testCase "metadata"  test_metadata
    , testCase "toFromSOP" test_toFromSOP
    ]

{-------------------------------------------------------------------------------
  Example values
-------------------------------------------------------------------------------}

recordA :: A.Record I [ "a" := Bool, "b" := Char ]
recordA =
      A.insert #a (I True)
    $ A.insert #b (I 'a')
    $ A.empty

recordS :: S.Record [ "a" := Bool, "b" := Char ]
recordS =
      S.insert #a True
    $ S.insert #b 'a'
    $ S.empty

{-------------------------------------------------------------------------------
  Auxiliary infrastructure

  Obviously the 'GetMetadata' instances are inductive and will result in
  quadratic core code blowup; they are here only for testing.
-------------------------------------------------------------------------------}

type family Fst (p :: (k1, k2)) :: k1 where Fst '(x, y) = x
type family Snd (p :: (k1, k2)) :: k2 where Snd '(x, y) = y

class GetMetadata (xs :: [(Symbol, Type)]) where
  getMetadata :: Proxy xs -> [(String, String)]

instance GetMetadata '[] where
  getMetadata _ = []

instance ( KnownSymbol (Fst x)
         , Typeable (Snd x)
         , GetMetadata xs
         ) => GetMetadata (x ': xs) where
  getMetadata _ =
        (symbolVal (Proxy @(Fst x)), show (typeRep (Proxy @(Snd x))))
      : getMetadata (Proxy @xs)

-- | Reflect field metadata from the type-level information
getMetadataA :: forall f r.
     GetMetadata (FieldTypes f r)
  => A.Record f r  -- ^ Serves as a proxy only
  -> [(String, String)]
getMetadataA _ = getMetadata (Proxy @(FieldTypes f r))

-- | Like 'getMetadataA', but for the simple API
getMetadataS :: forall r.
     GetMetadata (SimpleFieldTypes r)
  => S.Record r  -- ^ Serves as a proxy only
  -> [(String, String)]
getMetadataS _ = getMetadata (Proxy @(SimpleFieldTypes r))

{-------------------------------------------------------------------------------
  Tests proper

  TODO: We should have a test somewhere for normalization; this too depends
  on having accurate type-level information (it's what it was introduced for
  in the first place).
-------------------------------------------------------------------------------}

test_metadata :: Assertion
test_metadata = do
    assertEqual "advanced" expectedA $
      getMetadataA recordA
    assertEqual "simple" expectedS $
      getMetadataS recordS
  where
    expectedA, expectedS :: [(String, String)]
    expectedA = [
          ("a", "I Bool")
        , ("b", "I Char")
        ]
    expectedS = [
          ("a", "Bool")
        , ("b", "Char")
        ]

test_toFromSOP :: Assertion
test_toFromSOP =
    assertEqual "" (Just recordA) $
      (to . fromSOP <$> toSOP (from recordA))