covenant-1.1.0: test/kindcheck/Main.hs
module Main (main) where
import Covenant.ASG (defaultDatatypes)
import Covenant.Data ()
import Covenant.DeBruijn (DeBruijn (Z))
import Covenant.Index (count0, count1, ix0)
import Covenant.Test
( checkDataDecls,
checkEncodingArgs,
cycleCheck,
unsafeTyCon,
)
import Covenant.Type
( AbstractTy (BoundAt),
BuiltinFlatT (IntegerT),
CompT (Comp1),
CompTBody (ReturnT, (:--:>)),
Constructor (Constructor),
DataDeclaration (DataDeclaration, OpaqueData),
DataEncoding (PlutusData, SOP),
PlutusDataStrategy (ConstrData),
TyName,
ValT (Abstraction, BuiltinFlat, Datatype, ThunkT),
tyvar,
)
import Data.Map.Strict qualified as M
import Data.Vector qualified as V
import Optics.Core (view)
import Test.Tasty (TestTree, defaultMain, testGroup)
import Test.Tasty.ExpectedFailure (expectFail)
import Test.Tasty.HUnit (assertFailure, testCase)
main :: IO ()
main =
defaultMain . testGroup "DatatypeCycleCheck" $
[ testCase "singleNonRec" $ runCycleCheck [maybee],
testCase "singleSelfRec" $ runCycleCheck [intList],
expectFail $ testCase "mutRecShouldFail" (runCycleCheck [mutRec1, mutRec2]),
checkLedgerTypes,
simpleEncodingMismatch,
nestedThunkArg,
noThunkArgsToSOPTyCons,
goodSOPArg
]
checkLedgerTypes :: TestTree
checkLedgerTypes =
testCase "kindCheckLedgerTypes"
. either (assertFailure . show) pure
. checkDataDecls
. fmap (view #originalDecl)
$ defaultDatatypes
encodingCheck :: String -> [DataDeclaration AbstractTy] -> ValT AbstractTy -> TestTree
encodingCheck testNm tyDict valT =
testCase testNm $
either (assertFailure . show) pure $
checkEncodingArgs (view #datatypeEncoding) (mkTyDict tyDict) valT
shouldFailEncodingCheck :: String -> [DataDeclaration AbstractTy] -> ValT AbstractTy -> TestTree
shouldFailEncodingCheck tnm tyDict valT = expectFail $ encodingCheck tnm tyDict valT
simpleEncodingMismatch :: TestTree
simpleEncodingMismatch = shouldFailEncodingCheck "simpleEncodingMismatch" [maybee, intList] encodingMismatch
noThunkArgsToSOPTyCons :: TestTree
noThunkArgsToSOPTyCons =
shouldFailEncodingCheck "no thunk args to SOP tycons (for now)" [maybeSOP] badSOPThunk
nestedThunkArg :: TestTree
nestedThunkArg = shouldFailEncodingCheck "nestedThunkArg" [maybee] badThunkArg
goodSOPArg :: TestTree
goodSOPArg = encodingCheck "goodSOP" [maybeSOP] goodSOP
mkTyDict :: forall a. [DataDeclaration a] -> M.Map TyName (DataDeclaration a)
mkTyDict = foldr (\decl acc -> M.insert (view #datatypeName decl) decl acc) M.empty
runCycleCheck :: [DataDeclaration AbstractTy] -> IO ()
runCycleCheck decls = case cycleCheck declMap of
Nothing -> pure ()
Just err -> assertFailure $ show err
where
declMap =
foldr
( \dd acc -> case dd of
OpaqueData {} -> acc
DataDeclaration tn _ _ _ -> M.insert tn dd acc
)
M.empty
decls
maybee :: DataDeclaration AbstractTy
maybee = DataDeclaration "Maybe" count1 (V.fromList ctors) (PlutusData ConstrData)
where
ctors =
[ Constructor "Nothing" V.empty,
Constructor "Just" (V.singleton (Abstraction $ BoundAt Z ix0))
]
maybeSOP :: DataDeclaration AbstractTy
maybeSOP = DataDeclaration "MaybeSOP" count1 (V.fromList ctors) SOP
where
ctors =
[ Constructor "Nothing" V.empty,
Constructor "Just" (V.singleton (Abstraction $ BoundAt Z ix0))
]
intList :: DataDeclaration AbstractTy
intList = DataDeclaration "IntList" count0 (V.fromList ctors) SOP
where
ctors =
[ Constructor "Empty" V.empty,
Constructor "More" (V.fromList intListMore)
]
intListMore :: [ValT AbstractTy]
intListMore = [BuiltinFlat IntegerT, Datatype "IntList" V.empty]
-- DATA ENCODED MAYBE, SOP ENCODED INTLIST
-- Maybe IntList
encodingMismatch :: ValT AbstractTy
encodingMismatch = Datatype "Maybe" (V.fromList [Datatype "IntList" V.empty])
-- forall a. (a -> a)
identitee :: ValT AbstractTy
identitee = ThunkT $ Comp1 (tyvar Z ix0 :--:> ReturnT (tyvar Z ix0))
-- DATA ENCODED MAYBE
-- Maybe (Maybe (forall a. a -> a))
badThunkArg :: ValT AbstractTy
badThunkArg = unsafeTyCon "Maybe" [unsafeTyCon "Maybe" [identitee]]
-- SOP ENCODED MAYBE
-- Maybe (Maybe (Maybe Integer))
goodSOP :: ValT AbstractTy
goodSOP =
unsafeTyCon
"MaybeSOP"
[ unsafeTyCon "MaybeSOP" [unsafeTyCon "MaybeSOP" [BuiltinFlat IntegerT]]
]
-- NOTE Sean 7/2/2025: We are *temporarily* forbidding thunk arguments even to SOP encoded type constructors.
-- This is not strictly necessary, and we can go back and change that if we have time, but it
-- does greatly simplify getting a proof-of-concept off the ground.
-- SOP ENCODED MAYBE
-- Maybe (forall a. a -> a)
badSOPThunk :: ValT AbstractTy
badSOPThunk = unsafeTyCon "MaybeSOP" [identitee]
mutRec1 :: DataDeclaration AbstractTy
mutRec1 = DataDeclaration "MutRec1" count0 (V.fromList ctors) SOP
where
ctors = [Constructor "MutRec1" (V.singleton (Datatype "MutRec2" V.empty))]
mutRec2 :: DataDeclaration AbstractTy
mutRec2 = DataDeclaration "MutRec2" count0 (V.fromList ctors) SOP
where
ctors = [Constructor "MutRec2" (V.fromList [Datatype "MutRec1" V.empty])]