runGhcBWrap-core-0.1.0.0: src/RunGhc/MakeTest/FFI.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
module RunGhc.MakeTest.FFI where
import Data.Aeson
import Data.Kind (Type)
import GHC.TypeLits (Nat,TypeError, ErrorMessage(..))
import RunGhc.MakeTest.HKTs
--import RunGhc.MakeTest.TypeSig (RequireValid)
--------------------------------------------------------------------------------
-- Extract concrete type from a Slot
--------------------------------------------------------------------------------
type family SlotType (s :: Slot) :: Type where
SlotType ('MkSlot e t) = t
--------------------------------------------------------------------------------
-- Split into inputs and output
--------------------------------------------------------------------------------
type family InputSlots (xs :: [Slot]) :: [Slot] where
InputSlots '[] = TypeError ('Text "Empty signature")
InputSlots '[x] = '[]
InputSlots (x ': xs) = x ': InputSlots xs
type family OutputSlot (xs :: [Slot]) :: Slot where
OutputSlot '[] = TypeError ('Text "Empty signature")
OutputSlot '[x] = x
OutputSlot (x ': xs) = OutputSlot xs
--------------------------------------------------------------------------------
-- Convert slot list to tuple of concrete types
--------------------------------------------------------------------------------
type family ToTuple (xs :: [Slot]) :: Type where
ToTuple '[] = ()
ToTuple '[a] = SlotType a
ToTuple '[a, b] = (SlotType a, SlotType b)
ToTuple '[a, b, c] = (SlotType a, SlotType b, SlotType c)
ToTuple '[a, b, c, d] = (SlotType a, SlotType b, SlotType c, SlotType d)
ToTuple '[a, b, c, d, e] = (SlotType a, SlotType b, SlotType c, SlotType d, SlotType e)
ToTuple '[a, b, c, d, e, f] = (SlotType a, SlotType b, SlotType c, SlotType d, SlotType e, SlotType f)
ToTuple '[a, b, c, d, e, f, g] = (SlotType a, SlotType b, SlotType c, SlotType d, SlotType e, SlotType f, SlotType g)
ToTuple '[a, b, c, d, e, f, g, h] = (SlotType a, SlotType b, SlotType c, SlotType d, SlotType e, SlotType f, SlotType g, SlotType h)
--------------------------------------------------------------------------------
-- InputTuple and OutputType for Sig
--------------------------------------------------------------------------------
type family InputTuple (s :: Sig) :: Type where
InputTuple ('MkSig cs slots) = ToTuple (InputSlots (RequireValid slots))
type family OutputType (s :: Sig) :: Type where
OutputType ('MkSig cs slots) = SlotType (OutputSlot (RequireValid slots))
--------------------------------------------------------------------------------
-- Wrapper for a signature's values
--------------------------------------------------------------------------------
newtype SigVal (xs :: [Slot]) = SigVal { unSigVal :: ToTuple xs }
instance (FromJSON (ToTuple xs)) => FromJSON (SigVal xs) where
parseJSON v = SigVal <$> parseJSON v
instance (ToJSON (ToTuple xs)) => ToJSON (SigVal xs) where
toJSON (SigVal x) = toJSON x
--------------------------------------------------------------------------------
-- Extract var bindings from a TyExpr (returns list of (Nat, Type) pairs)
-- The Type is the concrete test type from the Slot, not from the TyExpr
--------------------------------------------------------------------------------
type family ExtractVars (e :: TyExpr) (t :: Type) :: [(Nat, Type)] where
ExtractVars ('TVar n) t = '[ '(n, t) ]
ExtractVars ('TConT _) _ = '[]
ExtractVars ('TCon1 _) _ = '[]
ExtractVars ('TCon2 _) _ = '[]
ExtractVars ('TApp f x) t = Append (ExtractVarsFromApp f t) (ExtractVarsFromApp x t)
-- For TApp, we can't easily know what concrete type the inner part has
-- So we just extract the var IDs and mark them with the outer type
-- This is a simplification - for full correctness you'd need to track types through application
type family ExtractVarsFromApp (e :: TyExpr) (t :: Type) :: [(Nat, Type)] where
ExtractVarsFromApp ('TVar n) t = '[ '(n, t) ]
ExtractVarsFromApp ('TConT _) _ = '[]
ExtractVarsFromApp ('TCon1 _) _ = '[]
ExtractVarsFromApp ('TCon2 _) _ = '[]
ExtractVarsFromApp ('TApp f x) t = Append (ExtractVarsFromApp f t) (ExtractVarsFromApp x t)
--------------------------------------------------------------------------------
-- Collect all var bindings from a slot list
--------------------------------------------------------------------------------
type family CollectVarBindings (xs :: [Slot]) :: [(Nat, Type)] where
CollectVarBindings '[] = '[]
CollectVarBindings ('MkSlot e t ': xs) = Append (ExtractVarsSimple e t) (CollectVarBindings xs)
-- Simplified: only check top-level TVar, not nested in TApp
-- This catches the common case: Var 0 Int, Var 0 Bool would conflict
type family ExtractVarsSimple (e :: TyExpr) (t :: Type) :: [(Nat, Type)] where
ExtractVarsSimple ('TVar n) t = '[ '(n, t) ]
ExtractVarsSimple ('TConT _) _ = '[]
ExtractVarsSimple ('TCon1 _) _ = '[]
ExtractVarsSimple ('TCon2 _) _ = '[]
ExtractVarsSimple ('TApp ('TCon1 f) ('TVar n)) t = '[ '(n, UnwrapApp1 f t) ]
ExtractVarsSimple ('TApp ('TApp ('TCon2 f) ('TVar n)) ('TVar m)) t =
'[ '(n, UnwrapApp2Fst f t), '(m, UnwrapApp2Snd f t) ]
ExtractVarsSimple ('TApp _ _) _ = '[] -- fallback for complex cases
-- Unwrap concrete type from App1: if t = [Int], unwrap to Int
type family UnwrapApp1 (f :: Type -> Type) (t :: Type) :: Type where
UnwrapApp1 f (f a) = a
UnwrapApp1 f t = t -- fallback
-- Unwrap first type arg from App2: if t = Either Int Bool, get Int
type family UnwrapApp2Fst (f :: Type -> Type -> Type) (t :: Type) :: Type where
UnwrapApp2Fst f (f a b) = a
UnwrapApp2Fst f t = t -- fallback
-- Unwrap second type arg from App2: if t = Either Int Bool, get Bool
type family UnwrapApp2Snd (f :: Type -> Type -> Type) (t :: Type) :: Type where
UnwrapApp2Snd f (f a b) = b
UnwrapApp2Snd f t = t -- fallback
--------------------------------------------------------------------------------
-- Find conflict in var bindings
--------------------------------------------------------------------------------
type family FindConflictInBindings (xs :: [(Nat, Type)]) :: Maybe (Nat, Type, Type) where
FindConflictInBindings '[] = 'Nothing
FindConflictInBindings ('(n, t) ': xs) = FindConflictFor n t xs (FindConflictInBindings xs)
type family FindConflictFor (n :: Nat) (t :: Type) (xs :: [(Nat, Type)]) (rest :: Maybe (Nat, Type, Type)) :: Maybe (Nat, Type, Type) where
FindConflictFor n t '[] rest = rest
FindConflictFor n t ('(n', t') ': xs) rest =
If (EqNat n n' && Not (EqType t t'))
('Just '(n, t, t'))
(FindConflictFor n t xs rest)
--------------------------------------------------------------------------------
-- RequireValid
--------------------------------------------------------------------------------
type family FindConflict (xs :: [Slot]) :: Maybe (Nat, Type, Type) where
FindConflict xs = FindConflictInBindings (CollectVarBindings xs)
type family RequireValid (xs :: [Slot]) :: [Slot] where
RequireValid xs = RequireValid' xs (FindConflict xs)
type family RequireValid' (xs :: [Slot]) (conflict :: Maybe (Nat, Type, Type)) :: [Slot] where
RequireValid' xs 'Nothing = xs
RequireValid' xs ('Just '(n, t1, t2)) = TypeError
( 'Text "Type signature conflict for type variable 'Var "
':<>: 'ShowType n ':<>: 'Text "'"
':$$: 'Text ""
':$$: 'Text " Used with type: " ':<>: 'ShowType t1
':$$: 'Text " Also used with: " ':<>: 'ShowType t2
':$$: 'Text ""
':$$: 'Text "Each type variable must map to exactly one concrete type."
)
type family UnwrapIOProxy (a :: Type) :: Type where
UnwrapIOProxy (IO a) = a -- IO a -> a, Maybe a -> a, [] a -> a
UnwrapIOProxy a = a -- Int -> Int, Bool -> Bool