packages feed

runGhcBWrap-core-0.1.0.0: src/RunGhc/MakeTest/HKTs.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE StandaloneKindSignatures #-}

module RunGhc.MakeTest.HKTs where

import Data.Kind (Type, Constraint)
import GHC.TypeLits
import Data.List (intercalate)
import Data.Char (chr, ord)
import Data.Proxy
import Data.Typeable

--------------------------------------------------------------------------------
-- Core type expression language
--------------------------------------------------------------------------------

data TyExpr
  = TVar Nat                       -- type variable: a, b, c
  | TConT Type                     -- concrete type: Int, Bool
  | TCon1 (Type -> Type)           -- 1-arg constructor: [], Maybe
  | TCon2 (Type -> Type -> Type)   -- 2-arg constructor: Either
  | TApp TyExpr TyExpr             -- application: f x

--------------------------------------------------------------------------------
-- Slot: pairs a type expression with its concrete test type
--------------------------------------------------------------------------------

data Slot = MkSlot TyExpr Type

--------------------------------------------------------------------------------
-- Constraints
--------------------------------------------------------------------------------

data Constraint' = Cst (Type -> Constraint) Nat

data Sig = MkSig [Constraint'] [Slot]

--------------------------------------------------------------------------------
-- Smart constructors
--------------------------------------------------------------------------------

-- Simple variable: a
type Var (n :: Nat) (t :: Type) = 'MkSlot ('TVar n) t

-- Fixed concrete type
type Fix (t :: Type) = 'MkSlot ('TConT t) t

-- App1: [a], Maybe a
type App1 (f :: Type -> Type) (n :: Nat) (t :: Type) = 
  'MkSlot ('TApp ('TCon1 f) ('TVar n)) (f t)

-- App2: Either a b
type App2 (f :: Type -> Type -> Type) (n :: Nat) (m :: Nat) (t1 :: Type) (t2 :: Type) = 
  'MkSlot ('TApp ('TApp ('TCon2 f) ('TVar n)) ('TVar m)) (f t1 t2)

-- App2L: Either Int a — left fixed
type App2L (f :: Type -> Type -> Type) (l :: Type) (n :: Nat) (t :: Type) = 
  'MkSlot ('TApp ('TApp ('TCon2 f) ('TConT l)) ('TVar n)) (f l t)

-- App2R: Either a Int — right fixed
type App2R (f :: Type -> Type -> Type) (n :: Nat) (r :: Type) (t :: Type) = 
  'MkSlot ('TApp ('TApp ('TCon2 f) ('TVar n)) ('TConT r)) (f t r)

-- Nested App1: [[a]], Maybe [a]
type App1Nested (f :: Type -> Type) (g :: Type -> Type) (n :: Nat) (t :: Type) = 
  'MkSlot ('TApp ('TCon1 f) ('TApp ('TCon1 g) ('TVar n))) (f (g t))

-- App1 with fixed inner: [Int] -> considered fixed but using App1 structure
type App1Fix (f :: Type -> Type) (t :: Type) =
  'MkSlot ('TApp ('TCon1 f) ('TConT t)) (f t)

-- For (a, [a]) pattern: a tuple where second element is a list of the first
type App2VarList (n :: Nat) (t :: Type) = 
  'MkSlot 
    ('TApp ('TApp ('TCon2 (,)) ('TVar n)) ('TApp ('TCon1 []) ('TVar n)))
    (t, [t])
--------------------------------------------------------------------------------
-- Type-level helpers
--------------------------------------------------------------------------------

type family EqNat (a :: Nat) (b :: Nat) :: Bool where
  EqNat a a = 'True
  EqNat a b = 'False

type family EqType (a :: Type) (b :: Type) :: Bool where
  EqType a a = 'True
  EqType a b = 'False

type family If (b :: Bool) (t :: k) (f :: k) :: k where
  If 'True  t f = t
  If 'False t f = f

type family (&&) (a :: Bool) (b :: Bool) :: Bool where
  'True && 'True = 'True
  _     && _     = 'False

type family Not (b :: Bool) :: Bool where
  Not 'True  = 'False
  Not 'False = 'True

type family Append (xs :: [k]) (ys :: [k]) :: [k] where
  Append '[] ys = ys
  Append (x ': xs) ys = x ': Append xs ys

--------------------------------------------------------------------------------
-- Extract all var IDs from a TyExpr
--------------------------------------------------------------------------------

type family GetVars (e :: TyExpr) :: [Nat] where
  GetVars ('TVar n)   = '[n]
  GetVars ('TConT _)  = '[]
  GetVars ('TCon1 _)  = '[]
  GetVars ('TCon2 _)  = '[]
  GetVars ('TApp f x) = Append (GetVars f) (GetVars x)

--------------------------------------------------------------------------------
-- Reify TyExpr to user-facing string
--------------------------------------------------------------------------------

class ReifyTyExpr (e :: TyExpr) where
  reifyTyExprUser :: Proxy e -> String

instance KnownNat n => ReifyTyExpr ('TVar n) where
  reifyTyExprUser _ = [chr (ord 'a' + fromIntegral (natVal (Proxy @n)))]

instance Typeable t => ReifyTyExpr ('TConT t) where
  reifyTyExprUser _ = show (typeRep (Proxy @t))

instance Typeable f => ReifyTyExpr ('TCon1 f) where
  reifyTyExprUser _ = show (typeRep (Proxy @f))

instance Typeable f => ReifyTyExpr ('TCon2 f) where
  reifyTyExprUser _ = show (typeRep (Proxy @f))

-- Special case: list application [x] instead of ([] x)
instance ReifyTyExpr x => ReifyTyExpr ('TApp ('TCon1 []) x) where
  reifyTyExprUser _ = "[" ++ reifyTyExprUser (Proxy @x) ++ "]"

-- General application
instance {-# OVERLAPPABLE #-} (ReifyTyExpr f, ReifyTyExpr x) => ReifyTyExpr ('TApp f x) where
  reifyTyExprUser _ = 
    let fStr = reifyTyExprUser (Proxy @f)
        xStr = reifyTyExprUser (Proxy @x)
        xWrapped = if ' ' `elem` xStr then "(" ++ xStr ++ ")" else xStr
    in fStr ++ " " ++ xWrapped

--------------------------------------------------------------------------------
-- Reify Slot
--------------------------------------------------------------------------------

class ReifySlot (s :: Slot) where
  slotUser :: Proxy s -> String
  slotTest :: Proxy s -> String

instance (ReifyTyExpr e, Typeable t) => ReifySlot ('MkSlot e t) where
  slotUser _ = reifyTyExprUser (Proxy @e)
  slotTest _ = show (typeRep (Proxy @t))

--------------------------------------------------------------------------------
-- Reify Slot list
--------------------------------------------------------------------------------

class ReifySlots (xs :: [Slot]) where
  reifyUser :: Proxy xs -> [String]
  reifyTest :: Proxy xs -> [String]

instance ReifySlots '[] where
  reifyUser _ = []
  reifyTest _ = []

instance (ReifySlot x, ReifySlots xs) => ReifySlots (x ': xs) where
  reifyUser _ = slotUser (Proxy @x) : reifyUser (Proxy @xs)
  reifyTest _ = slotTest (Proxy @x) : reifyTest (Proxy @xs)

--------------------------------------------------------------------------------
-- Reify Constraint
--------------------------------------------------------------------------------

class ReifyConstraint (c :: Constraint') where
  reifyConstraint :: Proxy c -> String

instance KnownNat n => ReifyConstraint ('Cst Eq n) where
  reifyConstraint _ = "Eq " ++ [chr (ord 'a' + fromIntegral (natVal (Proxy @n)))]

instance KnownNat n => ReifyConstraint ('Cst Ord n) where
  reifyConstraint _ = "Ord " ++ [chr (ord 'a' + fromIntegral (natVal (Proxy @n)))]

instance KnownNat n => ReifyConstraint ('Cst Show n) where
  reifyConstraint _ = "Show " ++ [chr (ord 'a' + fromIntegral (natVal (Proxy @n)))]

instance KnownNat n => ReifyConstraint ('Cst Num n) where
  reifyConstraint _ = "Num " ++ [chr (ord 'a' + fromIntegral (natVal (Proxy @n)))]

class ReifyConstraints (cs :: [Constraint']) where
  reifyConstraints :: Proxy cs -> [String]

instance ReifyConstraints '[] where
  reifyConstraints _ = []

instance (ReifyConstraint c, ReifyConstraints cs) => ReifyConstraints (c ': cs) where
  reifyConstraints _ = reifyConstraint (Proxy @c) : reifyConstraints (Proxy @cs)

--------------------------------------------------------------------------------
-- Reify full Sig
--------------------------------------------------------------------------------

class ReifySig (s :: Sig) where
  showUserSig :: Proxy s -> String
  showTestSig :: Proxy s -> String
  reifyUserSig :: Proxy s -> [String]
  reifyTestSig :: Proxy s -> [String]
  
instance (ReifyConstraints cs, ReifySlots slots) => ReifySig ('MkSig cs slots) where
  showUserSig _ = 
    let constraints = reifyConstraints (Proxy @cs)
        types = reifyUser (Proxy @slots)
        constraintPart = case constraints of
          [] -> ""
          [c] -> c ++ " => "
          cs' -> "(" ++ intercalate ", " cs' ++ ") => "
    in constraintPart ++ intercalate " -> " types
  
  showTestSig _ = intercalate " -> " (reifyTest (Proxy @slots))

  reifyUserSig _ = reifyUser (Proxy @slots)
  
  reifyTestSig _ = reifyTest (Proxy @slots)


-- Show just the output type (last slot) for user view
showUserOutputType :: ReifySig sig => Proxy sig -> String
showUserOutputType p = case reifyUserFromSig p of
  [] -> ""
  xs -> last xs

-- Show just the input types for user view
showUserInputTypes :: ReifySig sig => Proxy sig -> String
showUserInputTypes p = case reifyUserFromSig p of
  [] -> ""
  xs -> intercalate " -> " (init xs)

-- Helper to get the list of user-facing type strings from a Sig
reifyUserFromSig :: ReifySig sig => Proxy sig -> [String]
reifyUserFromSig = reifyUserSig
--------------------------------------------------------------------------------
-- ToConcrete: convert slot list to actual function type
--------------------------------------------------------------------------------

type family ToConcrete (xs :: [Slot]) :: Type where
  ToConcrete '[] = TypeError ('Text "Empty signature")
  ToConcrete '[ 'MkSlot _ t] = t
  ToConcrete ('MkSlot _ t ': xs) = t -> ToConcrete xs

--------------------------------------------------------------------------------
-- ToConcreteFromSig: extract slots from Sig and convert
--------------------------------------------------------------------------------

type family ToConcreteFromSig (s :: Sig) :: Type where
  ToConcreteFromSig ('MkSig cs slots) = ToConcrete slots

--------------------------------------------------------------------------------
-- Examples
--------------------------------------------------------------------------------

-- Simple: a -> a -> b
-- Test:   Int -> Int -> Bool
type Simple = '[ Var 0 Int, Var 0 Int, Var 1 Bool ]

-- With App1: [a] -> Maybe a -> a
-- Test:      [Int] -> Maybe Int -> Int
type WithApp1 = '[ App1 [] 0 Int, App1 Maybe 0 Int, Var 0 Int ]

-- With App2: Either a b -> a
-- Test:      Either Int Bool -> Int
type WithEither = '[ App2 Either 0 1 Int Bool, Var 0 Int ]

-- With App2L: Either String a -> a
-- Test:       Either String Int -> Int
type WithEitherL = '[ App2L Either String 0 Int, Var 0 Int ]

-- With App2R: Either a String -> a
-- Test:       Either Int String -> Int
type WithEitherR = '[ App2R Either 0 String Int, Var 0 Int ]

-- Nested: [[a]] -> [a]
-- Test:   [[Int]] -> [Int]
type Nested = '[ App1Nested [] [] 0 Int, App1 [] 0 Int ]

-- With constraint: Eq a => [a] -> Bool
type WithConstraint = 'MkSig 
  '[ 'Cst Eq 0 ] 
  '[ App1 [] 0 Int, Fix Bool ]

-- Multiple constraints: (Eq a, Ord b) => a -> b -> Bool
type MultiConstraint = 'MkSig
  '[ 'Cst Eq 0, 'Cst Ord 1 ]
  '[ Var 0 Int, Var 1 Char, Fix Bool ]

--------------------------------------------------------------------------------
-- Usage examples (in GHCi)
--------------------------------------------------------------------------------

-- >>> reifyUser (Proxy @Simple)
-- ["a", "a", "b"]

-- >>> reifyTest (Proxy @Simple)
-- ["Int", "Int", "Bool"]

-- >>> reifyUser (Proxy @WithApp1)
-- ["[a]", "Maybe a", "a"]

-- >>> reifyTest (Proxy @WithApp1)
-- ["[Int]", "Maybe Int", "Int"]

-- >>> reifyUser (Proxy @WithEither)
-- ["Either a b", "a"]

-- >>> reifyTest (Proxy @WithEither)
-- ["Either Int Bool", "Int"]

-- >>> showUserSig (Proxy @WithConstraint)
-- "Eq a => [a] -> Bool"

-- >>> showTestSig (Proxy @WithConstraint)
-- "[Int] -> Bool"

-- >>> showUserSig (Proxy @MultiConstraint)
-- "(Eq a, Ord b) => a -> b -> Bool"

-- >>> showTestSig (Proxy @MultiConstraint)
-- "Int -> Char -> Bool"

-- >>> :kind! ToConcrete Simple
-- ToConcrete Simple :: Type
-- = Int -> Int -> Bool

-- >>> :kind! ToConcreteFromSig WithConstraint
-- ToConcreteFromSig WithConstraint :: Type
-- = [Int] -> Bool


type App1Pair (f :: Type -> Type) (n :: Nat) (t :: Type) =
  'MkSlot 
    ('TApp ('TApp ('TCon2 (,)) ('TApp ('TCon1 f) ('TVar n))) ('TApp ('TCon1 f) ('TVar n)))
    (f t, f t)

-- Extract slots from Sig
type family SigSlots (s :: Sig) :: [Slot] where
  SigSlots ('MkSig cs slots) = slots

-- Special case: tuple application (a, b) instead of ((,) a b)
instance (ReifyTyExpr a, ReifyTyExpr b) => ReifyTyExpr ('TApp ('TApp ('TCon2 (,)) a) b) where
  reifyTyExprUser _ = "(" ++ reifyTyExprUser (Proxy @a) ++ ", " ++ reifyTyExprUser (Proxy @b) ++ ")"