covenant 1.2.0 → 1.3.0
raw patch · 19 files changed
+3609/−591 lines, 19 filesdep +aesondep +hex-textPVP ok
version bump matches the API change (PVP)
Dependencies added: aeson, hex-text
API changes (from Hackage documentation)
- Covenant.ASG: CataAlgebraWrongArity :: Int -> CovenantTypeError
- Covenant.ASG: CataApplyToNonValT :: ASGNodeType -> CovenantTypeError
- Covenant.ASG: CataNoBaseFunctorForType :: TyName -> CovenantTypeError
- Covenant.ASG: CataNoSuchType :: TyName -> CovenantTypeError
- Covenant.ASG: CataNotAnAlgebra :: ASGNodeType -> CovenantTypeError
- Covenant.ASG: CataUnsuitable :: CompT AbstractTy -> ValT AbstractTy -> CovenantTypeError
- Covenant.ASG: CataWrongBuiltinType :: BuiltinFlatT -> CovenantTypeError
- Covenant.ASG: CataWrongValT :: ValT AbstractTy -> CovenantTypeError
- Covenant.ASG: instance GHC.Classes.Eq Covenant.ASG.BoundTyVar
- Covenant.ASG: instance GHC.Classes.Ord Covenant.ASG.BoundTyVar
- Covenant.ASG: instance GHC.Show.Show Covenant.ASG.BoundTyVar
- Covenant.Prim: CaseData :: SixArgFunc
- Covenant.Prim: CaseList :: ThreeArgFunc
- Covenant.Type: BuiltinStrategy :: InternalStrategy -> DataEncoding
- Covenant.Type: PlutusData :: PlutusDataStrategy -> DataEncoding
- Covenant.Type: SOP :: DataEncoding
+ Covenant.ASG: BaseFunctorDoesNotExistFor :: TyName -> CovenantTypeError
+ Covenant.ASG: CataCouldNotRenameBB :: CompT AbstractTy -> Vector Ref -> CompT AbstractTy -> RenameError -> CovenantTypeError
+ Covenant.ASG: CataCouldNotRenameHandler :: CompT AbstractTy -> Vector Ref -> RenameError -> CovenantTypeError
+ Covenant.ASG: CataCouldNotRenameSubstitutions :: CompT AbstractTy -> Vector Ref -> CompT AbstractTy -> Vector (ValT AbstractTy) -> RenameError -> CovenantTypeError
+ Covenant.ASG: CataDidNotUnify :: CompT AbstractTy -> Vector Ref -> TypeAppError -> CovenantTypeError
+ Covenant.ASG: CataFixUpFailedForBB :: CompT AbstractTy -> Vector Ref -> Vector (ValT AbstractTy) -> ValT Renamed -> TypeAppError -> CovenantTypeError
+ Covenant.ASG: CataHandlerNotAValType :: ASGNodeType -> CovenantTypeError
+ Covenant.ASG: CataInvalidStructure :: CompT AbstractTy -> ValT AbstractTy -> CovenantTypeError
+ Covenant.ASG: CataMonomorphicBaseFunctor :: TyName -> CovenantTypeError
+ Covenant.ASG: CataNoTypeForBaseFunctor :: TyName -> CovenantTypeError
+ Covenant.ASG: CataNotADatatypeBaseFunctor :: ValT AbstractTy -> CovenantTypeError
+ Covenant.ASG: CataNotAValueType :: ASGNodeType -> CovenantTypeError
+ Covenant.ASG: CataUnexpectedResultType :: ValT AbstractTy -> ValT AbstractTy -> CovenantTypeError
+ Covenant.ASG: CataWrongArity :: CompT AbstractTy -> CovenantTypeError
+ Covenant.ASG: CataWrongNumberOfHandlers :: CompT AbstractTy -> Vector Ref -> CovenantTypeError
+ Covenant.ASG: CataWrongOutputType :: ValT AbstractTy -> ValT AbstractTy -> CovenantTypeError
+ Covenant.ASG: WrongNumInstantiationsInApp :: CompT Renamed -> Int -> Int -> CovenantTypeError
+ Covenant.ASG: app' :: (MonadHashCons Id ASGNode m, MonadError CovenantTypeError m, MonadReader ASGEnv m) => Id -> Vector Ref -> m Id
+ Covenant.ASG: baseFunctorOf :: (MonadError CovenantTypeError m, MonadReader ASGEnv m) => TyName -> m TyName
+ Covenant.ASG: ctor' :: (MonadHashCons Id ASGNode m, MonadError CovenantTypeError m, MonadReader ASGEnv m) => TyName -> ConstructorName -> Vector Ref -> m Id
+ Covenant.ASG: naturalBF :: TyName
+ Covenant.ASG: negativeBF :: TyName
+ Covenant.ASG: pattern ASG :: Map Id ASGNode -> ASG
+ Covenant.ASG: pattern Arg :: DeBruijn -> Index "arg" -> ValT AbstractTy -> Arg
+ Covenant.ASG: pattern Id :: Word64 -> Id
+ Covenant.ASG: topLevelId :: ASG -> Id
+ Covenant.Data: [_isBaseFunctor] :: DatatypeInfo var -> Bool
+ Covenant.Data: instance (k GHC.Types.~ Optics.Internal.Optic.Types.A_Lens, a GHC.Types.~ GHC.Types.Bool, b GHC.Types.~ GHC.Types.Bool) => Optics.Label.LabelOptic "isBaseFunctor" k (Covenant.Data.DatatypeInfo var) (Covenant.Data.DatatypeInfo var) a b
+ Covenant.Data: primBaseFunctorInfos :: Map TyName (DatatypeInfo AbstractTy)
+ Covenant.JSON: ASGCompilationFailure :: CovenantError -> SerializeErr
+ Covenant.JSON: ASGValidationFail :: CovenantError -> DeserializeErr
+ Covenant.JSON: DatatypeConversionFailure :: String -> SerializeErr
+ Covenant.JSON: JSONParseFailure :: String -> DeserializeErr
+ Covenant.JSON: Version :: Int -> Int -> Version
+ Covenant.JSON: [_major] :: Version -> Int
+ Covenant.JSON: [_minor] :: Version -> Int
+ Covenant.JSON: compileAndSerialize :: FilePath -> [DataDeclaration AbstractTy] -> ASGBuilder a -> Version -> ExceptT SerializeErr IO ()
+ Covenant.JSON: data DeserializeErr
+ Covenant.JSON: data SerializeErr
+ Covenant.JSON: data Version
+ Covenant.JSON: deserializeAndValidate :: FilePath -> ExceptT DeserializeErr IO ASG
+ Covenant.JSON: deserializeAndValidate_ :: FilePath -> IO ASG
+ Covenant.JSON: instance Data.Aeson.Types.FromJSON.FromJSON Covenant.JSON.CompilationUnit
+ Covenant.JSON: instance GHC.Classes.Eq Covenant.JSON.CompilationUnit
+ Covenant.JSON: instance GHC.Classes.Eq Covenant.JSON.DeserializeErr
+ Covenant.JSON: instance GHC.Classes.Eq Covenant.JSON.SerializeErr
+ Covenant.JSON: instance GHC.Classes.Eq Covenant.JSON.Version
+ Covenant.JSON: instance GHC.Classes.Ord Covenant.JSON.Version
+ Covenant.JSON: instance GHC.Show.Show Covenant.JSON.CompilationUnit
+ Covenant.JSON: instance GHC.Show.Show Covenant.JSON.DeserializeErr
+ Covenant.JSON: instance GHC.Show.Show Covenant.JSON.SerializeErr
+ Covenant.JSON: instance GHC.Show.Show Covenant.JSON.Version
+ Covenant.JSON: mkDatatypeInfos :: [DataDeclaration AbstractTy] -> Either String (Map TyName (DatatypeInfo AbstractTy))
+ Covenant.Test: UnsafeMkArg :: DeBruijn -> Index "arg" -> ValT AbstractTy -> Arg
+ Covenant.Test: UnsafeMkId :: Word64 -> Id
+ Covenant.Test: concretifyMegaTest :: ASGBuilder Id
+ Covenant.Test: concretifyMinimalBuilder :: ASGBuilder Id
+ Covenant.Test: conformanceDatatypes1 :: [DataDeclaration AbstractTy]
+ Covenant.Test: conformanceDatatypes2 :: [DataDeclaration AbstractTy]
+ Covenant.Test: data Arg
+ Covenant.Test: newtype Id
+ Covenant.Test: unsafeMkDatatypeInfos :: [DataDeclaration AbstractTy] -> Map TyName (DatatypeInfo AbstractTy)
+ Covenant.Type: [BuiltinStrategy] :: InternalStrategy -> DataEncoding
+ Covenant.Type: [PlutusData] :: PlutusDataStrategy -> DataEncoding
+ Covenant.Type: [SOP] :: DataEncoding
+ Covenant.Zipper: Tape :: [a] -> b -> [a] -> Tape a b
+ Covenant.Zipper: data ASGZipper a
+ Covenant.Zipper: data Tape a b
+ Covenant.Zipper: data ZipperAction
+ Covenant.Zipper: data ZipperState
+ Covenant.Zipper: instance Control.Monad.Action.Action Covenant.Zipper.ZipperAction
+ Covenant.Zipper: instance Control.Monad.Action.MonadUpdate Covenant.Zipper.ZipperAction Covenant.Zipper.ASGZipper
+ Covenant.Zipper: instance GHC.Base.Applicative Covenant.Zipper.ASGZipper
+ Covenant.Zipper: instance GHC.Base.Functor (Covenant.Zipper.Tape a)
+ Covenant.Zipper: instance GHC.Base.Functor Covenant.Zipper.ASGZipper
+ Covenant.Zipper: instance GHC.Base.Monad Covenant.Zipper.ASGZipper
+ Covenant.Zipper: instance GHC.Base.Monoid Covenant.Zipper.ZipperAction
+ Covenant.Zipper: instance GHC.Base.Semigroup Covenant.Zipper.ZipperAction
+ Covenant.Zipper: instance GHC.Classes.Eq Covenant.Zipper.ZipperStep
+ Covenant.Zipper: instance GHC.Show.Show Covenant.Zipper.ZipperStep
+ Covenant.Zipper: moveDown :: ZipperAction
+ Covenant.Zipper: moveLeft :: ZipperAction
+ Covenant.Zipper: moveRight :: ZipperAction
+ Covenant.Zipper: moveUp :: ZipperAction
+ Covenant.Zipper: pattern BrokenZipper :: ZipperState
+ Covenant.Zipper: pattern WorkingZipper :: [Tape Ref Id] -> Tape Ref (Either Arg (Id, ASGNode)) -> ZipperState
+ Covenant.Zipper: resetZipper :: ZipperAction
+ Covenant.Zipper: runASGZipper :: ASG -> ASGZipper a -> a
- Covenant.ASG: cata :: (MonadHashCons Id ASGNode m, MonadError CovenantTypeError m, MonadReader ASGEnv m) => Ref -> Ref -> m Id
+ Covenant.ASG: cata :: (MonadHashCons Id ASGNode m, MonadError CovenantTypeError m, MonadReader ASGEnv m) => CompT AbstractTy -> Vector Ref -> Ref -> m Id
- Covenant.ASG: pattern App :: Id -> Vector Ref -> ValNodeInfo
+ Covenant.ASG: pattern App :: Id -> Vector Ref -> Vector (Wedge BoundTyVar (ValT Void)) -> CompT AbstractTy -> ValNodeInfo
- Covenant.ASG: pattern Cata :: Ref -> Ref -> ValNodeInfo
+ Covenant.ASG: pattern Cata :: CompT AbstractTy -> Vector Ref -> Ref -> ValNodeInfo
- Covenant.Data: DatatypeInfo :: DataDeclaration var -> Maybe (DataDeclaration var, ValT var) -> Maybe (ValT var) -> DatatypeInfo var
+ Covenant.Data: DatatypeInfo :: DataDeclaration var -> Maybe (DataDeclaration var, ValT var) -> Maybe (ValT var) -> Bool -> DatatypeInfo var
- Covenant.Data: mkDatatypeInfo :: DataDeclaration AbstractTy -> Either BBFError (DatatypeInfo AbstractTy)
+ Covenant.Data: mkDatatypeInfo :: DataDeclaration AbstractTy -> Either BBFError (Map TyName (DatatypeInfo AbstractTy))
- Covenant.Type: pattern CompN :: Count "tyvar" -> CompTBody AbstractTy -> CompT AbstractTy
+ Covenant.Type: pattern CompN :: Count "tyvar" -> CompTBody a -> CompT a
Files
- CHANGELOG.md +23/−0
- covenant.cabal +13/−2
- src/Covenant/ASG.hs +473/−202
- src/Covenant/Data.hs +135/−20
- src/Covenant/Internal/KindCheck.hs +4/−1
- src/Covenant/Internal/Rename.hs +2/−2
- src/Covenant/Internal/Strategy.hs +7/−24
- src/Covenant/Internal/Term.hs +156/−48
- src/Covenant/Internal/Type.hs +16/−9
- src/Covenant/Internal/Unification.hs +103/−52
- src/Covenant/JSON.hs +1529/−0
- src/Covenant/Prim.hs +4/−30
- src/Covenant/Test.hs +254/−18
- src/Covenant/Type.hs +3/−2
- src/Covenant/Zipper.hs +281/−0
- test/asg/Main.hs +291/−149
- test/json-conformance/Main.hs +307/−0
- test/primops/Main.hs +6/−30
- test/type-applications/Main.hs +2/−2
CHANGELOG.md view
@@ -4,6 +4,29 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## UNRELEASED++* Expose unsafe constructors `UnsafeMkId` and `UnsafeMkArg` for `Id` and `Arg`+ respectively from `Covenant.Test`+* Add read-only synonyms `Id` and `Arg` for the two data types with the same+ name in `Covenant.ASG`+* Modify `CovenantTypeError` for new catamorphism related errors+* Rewrite `cata` to take explicit handlers for the base functor (similar to+ `match`)++## 1.3.0 -- 2025-10-07++* Zipper for the ASG in `Covenant.Zipper`+* `topLevelId` function in `Covenant.ASG`+* Changed type name representation of base functors from a `_F` suffix to a `#` prefix. For example, the base functor for `List` is now named `#List` instead of `List_F`. +* Changed base functor lookup machinery / representation to not require raw TyName text manipulation +* Provided helper function for safe construction of base functor names from the parent TyName+* `ASG` now exposes a read-only pattern synonym+* `ValNodeInfo`'s pattern for `App` now exposes type applications as well+* JSON serialization and deserialization support for `ASG`s with type+ declarations+* Removed `CaseData` and `CaseList`, as Plutus Core no longer supports them+ ## 1.2.0 -- 2025-08-27 * Helper function for safely retrieving an in-scope type variable when constructing the ASG
covenant.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: covenant-version: 1.2.0+version: 1.3.0 synopsis: Standalone IR for Cardano scripts. description: A library describing a call-by-push-value, Turner-total IR. Includes the ability to build up the IR programmatically.@@ -13,7 +13,7 @@ bug-reports: https://github.com/mlabs-haskell/covenant/issues copyright: (C) MLabs 2024-2025 category: Covenant-tested-with: ghc ==9.8.4 || ==9.10.2 || ==9.12.2+tested-with: ghc ==9.8.4 || ==9.10.3 || ==9.12.2 build-type: Simple extra-source-files: CHANGELOG.md@@ -32,6 +32,7 @@ -Wmisplaced-pragmas -Wmissing-export-lists -Wmissing-import-lists+ -Wunused-imports default-extensions: BangPatterns@@ -107,10 +108,12 @@ Covenant.Data Covenant.DeBruijn Covenant.Index+ Covenant.JSON Covenant.Prim Covenant.Test Covenant.Type Covenant.Util+ Covenant.Zipper other-modules: Covenant.Internal.KindCheck@@ -125,10 +128,12 @@ build-depends: QuickCheck ==2.15.0.1, acc ==0.2.0.3,+ aeson ==2.2.3.0, bimap ==0.5.0, bytestring >=0.12.1.0 && <0.13, containers >=0.6.8 && <0.8, enummapset ==0.7.3.0,+ hex-text ==0.1.0.9, mtl >=2.3.1 && <3, nonempty-vector ==0.2.4, optics-core ==0.4.1.1,@@ -193,5 +198,11 @@ type: exitcode-stdio-1.0 main-is: Main.hs hs-source-dirs: test/kindcheck++test-suite json-conformance+ import: test-lang+ type: exitcode-stdio-1.0+ main-is: Main.hs+ hs-source-dirs: test/json-conformance -- Benchmarks
src/Covenant/ASG.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE PatternSynonyms #-} -- |@@ -20,18 +21,19 @@ ( -- * The ASG itself -- ** Types- ASG,+ ASG (ASG), -- ** Functions+ topLevelId, topLevelNode, nodeAt, -- * ASG components -- ** Types- Id,+ Id (Id), Ref (..),- Arg,+ Arg (Arg), CompNodeInfo ( Builtin1, Builtin2,@@ -81,8 +83,13 @@ -- ** Helpers ctor,+ ctor', lazyLam, dtype,+ baseFunctorOf,+ naturalBF,+ negativeBF,+ app', -- *** Environment defaultDatatypes,@@ -98,7 +105,7 @@ import Data.Foldable (foldl') #endif -import Control.Monad (foldM, join, unless, zipWithM)+import Control.Monad (foldM, join, unless, zipWithM, (>=>)) import Control.Monad.Except ( ExceptT, MonadError (throwError),@@ -116,9 +123,9 @@ runReaderT, ) import Covenant.Constant (AConstant, typeConstant)-import Covenant.Data (DatatypeInfo, mkDatatypeInfo)+import Covenant.Data (DatatypeInfo, mkDatatypeInfo, primBaseFunctorInfos) import Covenant.DeBruijn (DeBruijn (S, Z), asInt)-import Covenant.Index (Count, Index, count0, intCount, intIndex, wordCount)+import Covenant.Index (Count, Index, count0, intCount, intIndex, ix0, wordCount) import Covenant.Internal.KindCheck (EncodingArgErr (EncodingArgMismatch), checkEncodingArgs) import Covenant.Internal.Ledger (ledgerTypes) import Covenant.Internal.Rename@@ -136,7 +143,8 @@ import Covenant.Internal.Term ( ASGNode (ACompNode, AValNode, AnError), ASGNodeType (CompNodeType, ErrorNodeType, ValNodeType),- Arg (Arg),+ Arg (UnsafeMkArg),+ BoundTyVar (BoundTyVar), CompNodeInfo ( Builtin1Internal, Builtin2Internal,@@ -149,16 +157,24 @@ ( ApplyCompType, ApplyToError, ApplyToValType,+ BaseFunctorDoesNotExistFor, BrokenIdReference,- CataAlgebraWrongArity,- CataApplyToNonValT,- CataNoBaseFunctorForType,- CataNoSuchType,+ CataCouldNotRenameBB,+ CataCouldNotRenameHandler,+ CataCouldNotRenameSubstitutions,+ CataDidNotUnify,+ CataFixUpFailedForBB,+ CataHandlerNotAValType,+ CataInvalidStructure,+ CataMonomorphicBaseFunctor,+ CataNoTypeForBaseFunctor, CataNonRigidAlgebra,- CataNotAnAlgebra,- CataUnsuitable,- CataWrongBuiltinType,- CataWrongValT,+ CataNotADatatypeBaseFunctor,+ CataNotAValueType,+ CataUnexpectedResultType,+ CataWrongArity,+ CataWrongNumberOfHandlers,+ CataWrongOutputType, ConstructorDoesNotExistForType, DatatypeInfoRenameError, EncodingError,@@ -193,9 +209,10 @@ UndeclaredOpaquePlutusDataCtor, UndoRenameFailure, UnificationError,+ WrongNumInstantiationsInApp, WrongReturnType ),- Id,+ Id (UnsafeMkId), Ref (AnArg, AnId), ValNodeInfo (AppInternal, CataInternal, DataConstructorInternal, LitInternal, MatchInternal, ThunkInternal), typeASGNode,@@ -227,6 +244,7 @@ ), UnifyM, checkApp,+ concretifyFT, fixUp, reconcile, runUnifyM,@@ -244,8 +262,8 @@ typeTwoArgFunc, ) import Covenant.Type- ( CompT (Comp0),- CompTBody (ReturnT),+ ( CompT (Comp0, Comp1, CompN),+ CompTBody (ArgsAndResult, ReturnT, (:--:>)), Constructor, ConstructorName, DataDeclaration (OpaqueData),@@ -253,8 +271,10 @@ Renamed (Unifiable), TyName (TyName), ValT (Abstraction),+ integerT, tyvar, )+import Covenant.Util (pattern ConsV, pattern NilV) import Data.Bimap (Bimap) import Data.Bimap qualified as Bimap import Data.Coerce (coerce)@@ -263,19 +283,18 @@ import Data.List (find) import Data.Map.Strict (Map) import Data.Map.Strict qualified as Map-import Data.Maybe (fromJust, isJust, mapMaybe)+import Data.Maybe (fromJust, fromMaybe, isJust, mapMaybe) import Data.Set qualified as Set import Data.Text qualified as T import Data.Vector (Vector) import Data.Vector qualified as Vector import Data.Vector.NonEmpty qualified as NonEmpty import Data.Void (Void, vacuous)-import Data.Wedge (Wedge, wedge)-import Data.Word (Word32)+import Data.Wedge (Wedge (Nowhere), wedge)+import Data.Word (Word32, Word64) import Optics.Core ( A_Lens, LabelOptic (labelOptic),- at, folded, ix, lens,@@ -289,10 +308,22 @@ _2, ) +-- | A read-only pattern for exposing the internals of an 'Id'.+--+-- @since 1.3.1+pattern Id :: Word64 -> Id+pattern Id w <- UnsafeMkId w++-- | A read-only pattern for exposing the internals of an 'Arg'.+--+-- @since 1.3.1+pattern Arg :: DeBruijn -> Index "arg" -> ValT AbstractTy -> Arg+pattern Arg db i t <- UnsafeMkArg db i t+ -- | A fully-assembled Covenant ASG. -- -- @since 1.0.0-newtype ASG = ASG (Id, Map Id ASGNode)+newtype ASG = ASGInternal (Id, Map Id ASGNode) deriving stock ( -- | @since 1.0.0 Eq,@@ -300,6 +331,12 @@ Show ) +{-# COMPLETE ASG #-}++-- | @since 1.3.0+pattern ASG :: Map Id ASGNode -> ASG+pattern ASG m <- ASGInternal (_, m)+ -- Note (Koz, 24/04/25): The `topLevelNode` and `nodeAt` functions use `fromJust`, -- because we can guarantee it's impossible to miss. For an end user, the only -- way to get hold of an `Id` is by inspecting a node, and since we control how@@ -310,11 +347,17 @@ -- unlikely and probably not worth trying to protect against, given the nuisance -- of having to work in `Maybe` all the time. +-- | Retrieves the top-level 'Id' of an ASG.+--+-- @since 1.3.0+topLevelId :: ASG -> Id+topLevelId (ASGInternal (i, _)) = i+ -- | Retrieves the top-level node of an ASG. -- -- @since 1.0.0 topLevelNode :: ASG -> ASGNode-topLevelNode asg@(ASG (rootId, _)) = nodeAt rootId asg+topLevelNode asg@(ASGInternal (rootId, _)) = nodeAt rootId asg -- | Given an 'Id' and an ASG, produces the node corresponding to that 'Id'. --@@ -327,7 +370,7 @@ -- -- @since 1.0.0 nodeAt :: Id -> ASG -> ASGNode-nodeAt i (ASG (_, mappings)) = fromJust . Map.lookup i $ mappings+nodeAt i (ASG mappings) = fromJust . Map.lookup i $ mappings -- | The environment used when \'building up\' an 'ASG'. This type is exposed -- only for testing, or debugging, and should /not/ be used in general by those@@ -435,12 +478,24 @@ pattern Lit :: AConstant -> ValNodeInfo pattern Lit c <- LitInternal c --- | An application of a computation (the 'Id' field) to some arguments (the--- 'Vector' field).+-- | An application of a computation (the 'Id' field) to some arguments. The+-- first 'Vector' argument contains the term arguments, while the second 'Vector'+-- argument contains the type arguments, as one of: ----- @since 1.0.0-pattern App :: Id -> Vector Ref -> ValNodeInfo-pattern App f args <- AppInternal f args+-- * 'Data.Wedge.Nowhere', meaning \'inferred\';+-- * 'Data.Wedge.Here', meaning \'a bound type variable from a parent scope\';+-- or+-- * 'Data.Wedge.There', meaning \'a concrete type\'.+--+-- The final CompT is the concretified function type, which is necessary for codegen.+-- @since 1.3.0+pattern App ::+ Id ->+ Vector Ref ->+ Vector (Wedge BoundTyVar (ValT Void)) ->+ CompT AbstractTy ->+ ValNodeInfo+pattern App f args instTys concreteFnTy <- AppInternal f args instTys concreteFnTy -- | Wrap a computation into a value (essentially delaying it). --@@ -448,11 +503,14 @@ pattern Thunk :: Id -> ValNodeInfo pattern Thunk i <- ThunkInternal i --- | \'Tear down\' a self-recursive value with an algebra.+-- | \'Tear down\' a self-recursive value with an algebra. The first argument is+-- the signature of the algebra used; the second argument is+-- a list of \'handlers\' for the base functor, represented similar to 'Match'+-- handlers, while the third is the type of thing to be torn down. ----- @since 1.0.0-pattern Cata :: Ref -> Ref -> ValNodeInfo-pattern Cata algebraRef valRef <- CataInternal algebraRef valRef+-- @since 1.4.0+pattern Cata :: CompT AbstractTy -> Vector Ref -> Ref -> ValNodeInfo+pattern Cata algT handlerRefs valRef <- CataInternal algT handlerRefs valRef -- | Inject (zero or more) fields into a data constructor --@@ -524,12 +582,12 @@ -- -- @since 1.1.0 defaultDatatypes :: Map TyName (DatatypeInfo AbstractTy)-defaultDatatypes = foldMap go ledgerTypes+defaultDatatypes = foldMap go ledgerTypes <> primBaseFunctorInfos where go :: DataDeclaration AbstractTy -> Map TyName (DatatypeInfo AbstractTy) go decl = case mkDatatypeInfo decl of Left err' -> error $ "Unexpected failure in default datatypes: " <> show err'- Right info -> Map.singleton (view #datatypeName decl) info+ Right info -> info -- | Executes an 'ASGBuilder' to make a \'finished\' ASG. --@@ -549,7 +607,7 @@ let (i, rootNode') = Bimap.findMax bm case rootNode' of AnError -> Left TopLevelError- ACompNode _ _ -> pure . ASG $ (i, Bimap.toMap bm)+ ACompNode _ _ -> pure . ASGInternal $ (i, Bimap.toMap bm) AValNode t info -> Left . TopLevelValue bm t $ info -- | Given a scope and a positional argument index, construct that argument.@@ -569,7 +627,7 @@ lookedUp <- asks (preview (#scopeInfo % #argumentInfo % ix scopeAsInt % _2 % ix indexAsInt)) case lookedUp of Nothing -> throwError . NoSuchArgument scope $ index- Just t -> pure . Arg scope index $ t+ Just t -> pure . UnsafeMkArg scope index . fixArgType scope $ t -- | Construct a node corresponding to the given Plutus primop. --@@ -662,7 +720,7 @@ cntW = view wordCount cnt bodyRef <- local (over (#scopeInfo % #argumentInfo) (Vector.cons (cntW, args))) bodyComp case bodyRef of- AnArg (Arg _ _ argTy) -> do+ AnArg (UnsafeMkArg _ _ argTy) -> do if argTy == resultT then refTo . ACompNode expectedT . LamInternal $ bodyRef else throwError . WrongReturnType resultT $ argTy@@ -703,16 +761,23 @@ -- * Not value types for 'Ref's -- * Renaming failures (likely due to a malformed function or argument type) ----- = Note+-- = Notes -- -- We use the 'Wedge' data type to designate type arguments, as it can represent -- the three possibilities we need: ----- * \'Infer this argument\', specified as 'Nowhere'.--- * \'Use this type variable in our scope\', specified as 'Here'.--- * \'Use this concrete type\', specified as 'There'.+-- * \'Infer this argument\', specified as 'Data.Wedge.Nowhere'.+-- * \'Use this type variable in our scope\', specified as 'Data.Wedge.Here'.+-- * \'Use this concrete type\', specified as 'Data.Wedge.There'. ----- @since 1.2.0+-- The *only* purpose of explicit type application arguments is to instantiate a tyvar in the result which is+-- not determined by any argument. These variables are instantiated after every other argument has been concretified.+--+-- For example, if you have a function+-- @f :: forall a b c. (a -> b) -> (b -> a) -> b -> Either a c@+-- Then you will need to supply _one_ explicit type application to concretify @c@.+--+-- @since 1.3.0 app :: forall (m :: Type -> Type). (MonadHashCons Id ASGNode m, MonadError CovenantTypeError m, MonadReader ASGEnv m) =>@@ -728,17 +793,33 @@ case lookedUp of CompNodeType fT -> case runRenameM scopeInfo . renameCompT $ fT of Left err' -> throwError . RenameFunctionFailed fT $ err'- Right renamedFT -> do- instantiatedFT <- instantiate subs renamedFT- renamedArgs <- traverse renameArg argRefs- tyDict <- asks (view #datatypeInfo)- result <- either (throwError . UnificationError) pure $ checkApp tyDict instantiatedFT (Vector.toList renamedArgs)- restored <- undoRenameM result- checkEncodingWithInfo tyDict restored- refTo . AValNode restored . AppInternal fId $ argRefs+ Right renamedFT@(CompT count _) -> do+ let numInstantiations = Vector.length instTys+ numVars = review intCount count+ if numInstantiations /= numVars+ then throwError $ WrongNumInstantiationsInApp renamedFT numVars numInstantiations+ else do+ renamedArgs <- traverse renameArg argRefs+ let concretifiedFT = concretifyFT renamedFT renamedArgs+ instantiatedFT <- instantiate subs concretifiedFT+ tyDict <- asks (view #datatypeInfo)+ result <- either (throwError . UnificationError) pure $ checkApp tyDict instantiatedFT (Vector.toList renamedArgs)+ restored <- undoRenameM result+ unRenamedFnTy <- undoRenameCompT instantiatedFT+ checkEncodingWithInfo tyDict restored+ refTo . AValNode restored $ AppInternal fId argRefs instTys unRenamedFnTy ValNodeType t -> throwError . ApplyToValType $ t ErrorNodeType -> throwError ApplyToError where+ renameSubs :: [(Index "tyvar", ValT AbstractTy)] -> m [(Index "tyvar", ValT Renamed)]+ renameSubs subs =+ askScope >>= \scope -> case traverse (traverse (runRenameM scope . renameValT)) subs of+ Left err' -> throwError $ FailedToRenameInstantiation err'+ Right res -> pure res++ -- NOTE: The helper function below only concerns instantiations that result from+ -- explicit type applications (via the third argument to `app`).+ -- mkSubstitutions :: Vector (Wedge BoundTyVar (ValT Void)) -> [(Index "tyvar", ValT AbstractTy)] mkSubstitutions = Vector.ifoldl'@@ -752,14 +833,7 @@ ) [] - renameSubs :: [(Index "tyvar", ValT AbstractTy)] -> m [(Index "tyvar", ValT Renamed)]- renameSubs subs =- askScope >>= \scope -> case traverse (traverse (runRenameM scope . renameValT)) subs of- Left err' -> throwError $ FailedToRenameInstantiation err'- Right res -> pure res- instantiate :: [(Index "tyvar", ValT Renamed)] -> CompT Renamed -> m (CompT Renamed)- instantiate [] fn = pure fn instantiate subs fn = do instantiated <- liftUnifyM . fixUp $ foldr (\(i, t) f -> substitute i t f) (ThunkT fn) subs case instantiated of@@ -787,7 +861,7 @@ -- case. -- -- We resolve this problem by returning a thunk. In the case of our example,--- @Nothing@ would produce @<forall a . !Maybe a>@.+-- @Nothing@ would produce @\<forall a . !Maybe a\>@. -- -- @since 1.2.0 dataConstructor ::@@ -913,7 +987,7 @@ m (Constructor a) findConstructor xs = case find (\x -> view #constructorName x == ctorName) xs of Nothing -> throwError $ ConstructorDoesNotExistForType tyName ctorName- Just ctor' -> pure ctor'+ Just ctor'' -> pure ctor'' -- Looks up the DatatypeInfo for the type argument supplied -- and also renames (and rethrows the rename error if renaming fails)@@ -951,76 +1025,130 @@ ValNodeType t -> throwError . ThunkValType $ t ErrorNodeType -> throwError ThunkError --- | Given a 'Ref' to an algebra (that is, something taking a base functor and--- producing some result), and a 'Ref' to a value associated with that base--- functor, build a catamorphism to tear it down. This can fail for a range of--- reasons:------ * First 'Ref' is not a thunk taking one argument--- * The argument to the thunk isn't a base functor, or isn't a suitable base--- functor for the second argument--- * Second argument is not a value type+-- | Given a computation type for an algebra (the \'stated algebra type\'), as+-- well as a set of handlers designed to deal with each case of the stated+-- algebra's base functor, plus a 'Ref' to a value associated with that base+-- functor, build a catamorphism to tear that value down. ----- = Note+-- Ensure the following: ----- 'cata' cannot work with /non-rigid/ algebras; that is, all algebras must be--- functions that bind no type variables of their own.+-- * The stated algebra type must be a 'Comp0' taking a base functor and+-- returning the same type as the last type variable instantiation of that base+-- functor.+-- * The handlers must be provided in the same order as the constructors of the+-- parameter of the stated algebra type. Handlers for \'arms\' with no fields+-- must be non-thunks, while handlers for \'arms\' with fields must be thunks.+-- * The third argument must be a value type with a base functor. ----- @since 1.1.0+-- @since 1.3.0 cata :: forall (m :: Type -> Type). (MonadHashCons Id ASGNode m, MonadError CovenantTypeError m, MonadReader ASGEnv m) =>- Ref ->+ CompT AbstractTy ->+ Vector Ref -> Ref -> m Id-cata rAlg rVal =- typeRef rVal >>= \case- ValNodeType valT ->- typeRef rAlg >>= \case- t@(ValNodeType (ThunkT algT)) -> case algT of- Comp0 (CompTBody nev) -> do- let algebraArity = arity algT- unless (algebraArity == 1) (throwError . CataAlgebraWrongArity $ algebraArity)- case nev NonEmpty.! 0 of- Datatype bfName bfTyArgs -> do- -- If we got this far, we know at minimum that we have somewhat- -- sensical arguments. Now we have to make sure that we have a- -- suitable type for the algebra, and a suitable thing to tear- -- down.- --- -- After verifying this, we use `tryApply` so the unification- -- machinery can produce the type we expect with proper- -- concretifications.- unless (Vector.length bfTyArgs > 0) (throwError . CataNotAnAlgebra $ t)- let lastTyArg = Vector.last bfTyArgs- unless (nev NonEmpty.! 1 == lastTyArg) (throwError . CataNotAnAlgebra $ t)- appliedArgT <- case valT of- BuiltinFlat bT -> case bT of- ByteStringT -> do- unless (bfName == "ByteString_F") (throwError . CataUnsuitable algT $ valT)- pure $ Datatype "ByteString_F" . Vector.singleton $ lastTyArg- IntegerT -> do- let isSuitableBaseFunctor = bfName == "Natural_F" || bfName == "Negative_F"- unless isSuitableBaseFunctor (throwError . CataUnsuitable algT $ valT)- pure $ Datatype bfName . Vector.singleton $ lastTyArg- _ -> throwError . CataWrongBuiltinType $ bT- Datatype tyName tyVars -> do- lookedUp <- asks (view (#datatypeInfo % at tyName))- case lookedUp of- Nothing -> throwError . CataNoSuchType $ tyName- Just info -> case view #baseFunctor info of- Just (DataDeclaration actualBfName _ _ _, _) -> do- unless (bfName == actualBfName) (throwError . CataUnsuitable algT $ valT)- let lastTyArg' = stepDownDB lastTyArg- pure . Datatype bfName . Vector.snoc tyVars $ lastTyArg'- _ -> throwError . CataNoBaseFunctorForType $ tyName- _ -> throwError . CataWrongValT $ valT- resultT <- tryApply algT appliedArgT- refTo . AValNode resultT . CataInternal rAlg $ rVal- _ -> throwError . CataNotAnAlgebra $ t- _ -> throwError . CataNonRigidAlgebra $ algT- t -> throwError . CataNotAnAlgebra $ t- t -> throwError . CataApplyToNonValT $ t+cata algT handlers rVal =+ getCataInfo algT >>= \case+ (resultT, IntegerCata) ->+ typeRef rVal >>= \case+ ValNodeType (BuiltinFlat IntegerT) -> tryApply resultT integerBB+ ValNodeType t -> throwError . CataInvalidStructure algT $ t+ t -> throwError . CataNotAValueType $ t+ (resultT, ByteStringCata) ->+ typeRef rVal >>= \case+ ValNodeType (BuiltinFlat ByteStringT) -> tryApply resultT bsBB+ ValNodeType t -> throwError . CataInvalidStructure algT $ t+ t -> throwError . CataNotAValueType $ t+ (resultT, RigidCata expectedTyName bbForm) ->+ typeRef rVal >>= \case+ ValNodeType t@(Datatype tyName _) -> do+ unless (tyName == expectedTyName) (throwError . CataInvalidStructure algT $ t)+ tryApply resultT bbForm+ ValNodeType t -> throwError . CataInvalidStructure algT $ t+ t -> throwError . CataNotAValueType $ t+ (resultT, NonRigidCata expectedTyName bbf) ->+ typeRef rVal >>= \case+ ValNodeType t@(Datatype tyName tyVars) -> do+ unless (tyName == expectedTyName) (throwError . CataInvalidStructure algT $ t)+ tryApplySubstituting resultT bbf tyVars+ ValNodeType t -> throwError . CataInvalidStructure algT $ t+ t -> throwError . CataNotAValueType $ t+ where+ integerBB :: CompT AbstractTy+ integerBB =+ Comp1 $+ tyvar Z ix0+ :--:> ThunkT (Comp0 $ tyvar (S Z) ix0 :--:> ReturnT (tyvar (S Z) ix0))+ :--:> ReturnT (tyvar Z ix0)+ bsBB :: CompT AbstractTy+ bsBB =+ Comp1 $+ tyvar Z ix0+ :--:> ThunkT (Comp0 $ integerT :--:> tyvar (S Z) ix0 :--:> ReturnT (tyvar (S Z) ix0))+ :--:> ReturnT (tyvar Z ix0)+ tryApplySubstituting :: ValT AbstractTy -> ValT AbstractTy -> Vector (ValT AbstractTy) -> m Id+ tryApplySubstituting resultT bbThunk subs = do+ scopeInfo <- askScope+ tyDict <- asks (view #datatypeInfo)+ case runRenameM scopeInfo . renameValT $ bbThunk of+ Right renamedBBThunk -> case traverse (runRenameM scopeInfo . renameValT) subs of+ Right renamedSubs -> case runUnifyM tyDict (fixUp . doSubsVal renamedSubs $ renamedBBThunk) of+ Right substitutedBBThunk -> do+ asArgs <- traverse handlerToArg handlers+ case traverse (runRenameM scopeInfo . renameValT) asArgs of+ Right renamedHandlers -> case checkApp tyDict (fromThunk substitutedBBThunk) (fmap Just . Vector.toList $ renamedHandlers) of+ Right result -> do+ restored <- undoRenameM result+ unless (restored == resultT) (throwError . CataUnexpectedResultType resultT $ restored)+ refTo . AValNode restored . CataInternal algT handlers $ rVal+ Left err' -> throwError . CataDidNotUnify algT handlers $ err'+ Left err' -> throwError . CataCouldNotRenameHandler algT handlers $ err'+ Left err' -> throwError . CataFixUpFailedForBB algT handlers subs renamedBBThunk $ err'+ Left err' -> throwError . CataCouldNotRenameSubstitutions algT handlers (fromThunk bbThunk) subs $ err'+ Left err' -> throwError . CataCouldNotRenameBB algT handlers (fromThunk bbThunk) $ err'+ fromThunk :: forall (a :: Type). (Show a) => ValT a -> CompT a+ fromThunk = \case+ ThunkT t -> t+ t -> error $ "cata: ValT given by getCataInfo wasn't a thunk as expected" <> show t+ tryApply :: ValT AbstractTy -> CompT AbstractTy -> m Id+ tryApply resultT bb = do+ let bbArity = arity bb+ unless (bbArity == Vector.length handlers) (throwError . CataWrongNumberOfHandlers algT $ handlers)+ scopeInfo <- askScope+ case runRenameM scopeInfo . renameCompT $ bb of+ Right renamedBB -> do+ tyDict <- asks (view #datatypeInfo)+ asArgs <- traverse handlerToArg handlers+ case traverse (runRenameM scopeInfo . renameValT) asArgs of+ Right renamedHandlers -> case checkApp tyDict renamedBB (fmap Just . Vector.toList $ renamedHandlers) of+ Right result -> do+ restored <- undoRenameM result+ unless (restored == resultT) (throwError . CataUnexpectedResultType resultT $ restored)+ refTo . AValNode restored . CataInternal algT handlers $ rVal+ Left err' -> throwError . CataDidNotUnify algT handlers $ err'+ Left err' -> throwError . CataCouldNotRenameHandler algT handlers $ err'+ Left err' -> throwError . CataCouldNotRenameBB algT handlers bb $ err'+ handlerToArg :: Ref -> m (ValT AbstractTy)+ handlerToArg =+ typeRef >=> \case+ ValNodeType t -> pure t+ t -> throwError . CataHandlerNotAValType $ t+ doSubsComp :: Vector (ValT Renamed) -> CompT Renamed -> CompT Renamed+ doSubsComp subs (CompT count (CompTBody nev)) = CompT count . CompTBody . fmap (doSubsVal subs) $ nev+ doSubsVal :: Vector (ValT Renamed) -> ValT Renamed -> ValT Renamed+ doSubsVal subs = \case+ -- Note (Koz, 11/11/2025): The indexing ends up being a bit strange here,+ -- as the _last_ tyvar in a BB form is always meant to stay abstract.+ -- However, this means that if we have `n` substitutions, we have `n + 1`+ -- possible unifiables for those substitutions to go into. Thus, doing a+ -- blind indexing into `subs` can blow up.+ --+ -- Thus, if we 'miss', we should leave it alone.+ t@(Abstraction (Unifiable i)) -> fromMaybe t $ subs Vector.!? review intIndex i+ ThunkT someComp -> ThunkT . doSubsComp subs $ someComp+ Datatype tyName tyVars -> Datatype tyName . fmap (doSubsVal subs) $ tyVars+ t -> t -- | Perform a pattern match. The first argument is the value to be matched on, -- and the second argument is a 'Vector' of \'handlers\' for each possible@@ -1030,6 +1158,33 @@ -- Polymorphic \'handlers\' (that is, thunks whose computation binds type -- variables of its own) will fail to compile. --+-- = Note+--+-- Opaque the handlers for an opaque type must follow the order:+--+-- @+-- [ PlutusI,+-- PlutusB,+-- PlutusConstr,+-- PlutusMap,+-- PlutusList+-- ]+-- @+--+-- Where types not included in the provided constructors to the opaque declaration are omitted.+--+-- Furthermore, the handlers for opaque constructors operate on the unwrapped arguments to+-- their respective PlutusData constructor. That is, for some result type @r@, the handlers for a+-- an opaque type which uses all the constructors should have the types:+--+-- @+-- PlutusI :: Integer -> r+-- PlutusB :: ByteString -> r+-- PlutusConstr :: Integer -> [Data] -> r+-- PlutusMap :: [(Integer,Data)] -> r+-- PlutusList :: [Data] -> r+-- @+-- -- @since 1.2.0 match :: forall (m :: Type -> Type).@@ -1084,7 +1239,7 @@ Right res -> pure res -- The type constructor for the base-functor variant of the scrutinee type. let scrut = Datatype tn tyConArgs- let scrutF = Datatype (TyName $ rawTn <> "_F") (Vector.snoc tyConArgs scrut)+ let scrutF = Datatype (TyName $ "#" <> rawTn) (Vector.snoc tyConArgs scrut) -- These are arguments to the original type constructor plus the snoc'd original type. -- E.g. if we have: -- Scrutinee: List Int@@ -1144,54 +1299,6 @@ -- Helpers --- Note (Koz, 13/08/2025): We need this procedure specifically for `cata`. The--- reason for this has to do with how we construct the 'base functor form' of--- the value to be torn down by the catamorphism, in order to use the--- unification machinery to get the type of the final result.------ To be specific, suppose we have `<List_F r (Maybe r) -> !Maybe r>` as our algebra--- argument (where `r` is some rigid), and `List r` as the value to be torn--- down. If we assume the rigid is bound one scope away, `r`'s DeBruijn index--- will be `S Z` for--- the value to be torn down, but `S (S Z)` for the algebra argument. The way--- our approach works is:------ 1. Look at the algebra argument, specifically the base functor type. Take its--- last type argument, which we will call `last`.--- 2. Determine the base functor for the value to be torn down. Cook up a new--- instance of the base functor type, copying all the type arguments from the--- value to be torn down in the same order. Then put `last` at the end.--- 3. Force the algebra argument thunk, then try and apply the result of Step 2--- to that.------ Following the steps above for our example, we would proceed as follows:------ 1. Set `last` as `Maybe r`.--- 2. Cook up `List_F r (Maybe r)`. Note that this matches what the algebra--- expects.--- 3. Use the unifier with `List_F r (Maybe r) -> !Maybe r`, applying the--- argument `List_F r (Maybe r)` from Step 2.------ However, if `last` is a rigid, we have an 'off by one error'. To see why,--- consider the form of the algebra argument:------ `ThunkT . Comp0 $ Datatype "List_F" [tyvar (S (S Z)) ix0, ....`------ However, `tyvar (S (S Z)) ix0` is not valid in the scope of the value to be--- torn down: that same rigid would have DeBruijn index `S Z` there instead.--- This applies the same if the tyvar is part of a datatype.------ As we prohibit non-rigid algebras, this requires us to lower the DeBruijn--- index by one for our process.-stepDownDB :: ValT AbstractTy -> ValT AbstractTy-stepDownDB = \case- Abstraction (BoundAt db i) -> case db of- -- This is impossible, so we just return it unmodified- Z -> Abstraction (BoundAt db i)- (S db') -> Abstraction (BoundAt db' i)- Datatype tyName tyArgs -> Datatype tyName . fmap stepDownDB $ tyArgs- x -> x- renameArg :: forall (m :: Type -> Type). (MonadHashCons Id ASGNode m, MonadError CovenantTypeError m, MonadReader ASGEnv m) =>@@ -1216,37 +1323,8 @@ Left encErr -> throwError $ EncodingError encErr Right {} -> pure () -tryApply ::- forall (m :: Type -> Type).- (MonadError CovenantTypeError m, MonadReader ASGEnv m) =>- CompT AbstractTy ->- ValT AbstractTy ->- m (ValT AbstractTy)-tryApply algebraT argT =- askScope >>= \scope -> case runRenameM scope . renameCompT $ algebraT of- Left err' -> throwError . RenameFunctionFailed algebraT $ err'- Right renamedAlgebraT -> case runRenameM scope . renameValT $ argT of- Left err' -> throwError . RenameArgumentFailed argT $ err'- Right renamedArgT -> do- tyDict <- asks (view #datatypeInfo)- case checkApp tyDict renamedAlgebraT [Just renamedArgT] of- Left err' -> throwError . UnificationError $ err'- Right resultT -> undoRenameM resultT- -- Putting this here to reduce chance of annoying manual merge (will move later) --- | Wrapper around an `Arg` that we know represents an in-scope type variable.--- @since 1.2.0-data BoundTyVar = BoundTyVar DeBruijn (Index "tyvar")- deriving stock- ( -- @since 1.2.0- Show,- -- @since 1.2.0- Eq,- -- @since 1.2.0- Ord- )- -- | Given a DeBruijn index (designating scope) and positional index (designating -- which variable in that scope we are interested in), retrieve an in-scope type -- variable.@@ -1291,6 +1369,17 @@ Left err' -> throwError $ UndoRenameFailure err' Right renamed -> pure renamed +undoRenameCompT ::+ forall (m :: Type -> Type).+ (MonadError CovenantTypeError m, MonadReader ASGEnv m) =>+ CompT Renamed ->+ m (CompT AbstractTy)+undoRenameCompT comp =+ undoRenameM (ThunkT comp) >>= \case+ ThunkT res -> pure res+ -- This really should be impossible, not just unlikely.+ _other -> error "Undoing renaming on a CompT resulting in something other than a thunk, which should be totally impossible"+ askScope :: forall (m :: Type -> Type). (MonadReader ASGEnv m) =>@@ -1325,7 +1414,7 @@ -- -- Consider @Left 3@. In this case, the field only determines the first type -- argument to the @Either@ data type, and if we used 'dataConstructor', we--- would be left with a thunk of type @<forall a . !Either Integer a>@. Using+-- would be left with a thunk of type @\<forall a . !Either Integer a\>@. Using -- 'ctor', we can immediately specify what @a@ should be, and unwrap the thunk. -- -- @since 1.2.0@@ -1342,6 +1431,40 @@ dataForced <- force (AnId dataThunk) app dataForced mempty instTys +-- | 'ctor' without the instantiation arguments, which are left up to inference.+--+-- @since 1.3.0+ctor' ::+ forall (m :: Type -> Type).+ (MonadHashCons Id ASGNode m, MonadError CovenantTypeError m, MonadReader ASGEnv m) =>+ TyName ->+ ConstructorName ->+ Vector.Vector Ref ->+ m Id+ctor' tn cn args = do+ dataThunk <- dataConstructor tn cn args+ dataForced <- force (AnId dataThunk)+ app' dataForced mempty++-- | A variant of `app` which does not take a 'Vector' of type instantiation+-- arguments and instead will try to infer all type arguments.+--+-- @since 1.3.0+app' ::+ forall (m :: Type -> Type).+ (MonadHashCons Id ASGNode m, MonadError CovenantTypeError m, MonadReader ASGEnv m) =>+ Id ->+ Vector Ref ->+ m Id+app' fId args =+ typeId fId >>= \case+ CompNodeType (CompT count _) -> do+ let numVars = review intCount count+ instArgs = Vector.replicate numVars Nowhere+ app fId args instArgs+ ValNodeType t -> throwError . ApplyToValType $ t+ ErrorNodeType -> throwError ApplyToError+ -- | As 'lam', but produces a thunk value instead of a computation. -- -- @since 1.2.0@@ -1358,3 +1481,151 @@ -- @since 1.2.0 dtype :: TyName -> [ValT AbstractTy] -> ValT AbstractTy dtype tn = Datatype tn . Vector.fromList++-- | Helper for constructing a base functor name without having know the internal naming convention for+-- base functors.+--+-- @since 1.3.0+baseFunctorOf ::+ forall (m :: Type -> Type).+ (MonadError CovenantTypeError m, MonadReader ASGEnv m) =>+ TyName ->+ m TyName+baseFunctorOf (TyName tn) = do+ let bfTn = TyName ("#" <> tn)+ tyDict <- asks (view #datatypeInfo)+ case preview (ix bfTn) tyDict of+ Nothing -> throwError $ BaseFunctorDoesNotExistFor (TyName tn)+ Just {} -> pure bfTn++-- | The name of the @Natural@ base functor for @Integer@.+--+-- This is required because @Integer@ is the only type with two base functors,+-- and thus, its base functor cannot be determined from the type name alone.+--+-- @since 1.3.0+naturalBF :: TyName+naturalBF = TyName "#Natural"++-- | The name of the @Negative@ base functor for @Integer@.+--+-- This is required because @Integer@ is the only type with two base functors,+-- and thus, its base functor cannot be determined from the type name alone.+--+-- @since 1.3.0+negativeBF :: TyName+negativeBF = TyName "#Negative"++-- Used as a helper for catamorphisms to classify what we need to do based on+-- the stated algebra type+data CataInfo+ = IntegerCata+ | ByteStringCata+ | RigidCata TyName (CompT AbstractTy)+ | NonRigidCata TyName (ValT AbstractTy)++getCataInfo ::+ forall (m :: Type -> Type).+ (MonadReader ASGEnv m, MonadError CovenantTypeError m) =>+ CompT AbstractTy -> m (ValT AbstractTy, CataInfo)+getCataInfo t = case t of+ Comp0 (CompTBody nev) -> do+ unless (NonEmpty.length nev == 2) (throwError . CataWrongArity $ t)+ let inputT = nev NonEmpty.! 0+ let outputT = nev NonEmpty.! 1+ case inputT of+ Datatype bfTyName bfTyVars ->+ (stepDown outputT,) <$> case Vector.unsnoc bfTyVars of+ Just (tyVarInsts, lastT) -> do+ unless (lastT == outputT) (throwError . CataWrongOutputType outputT $ lastT)+ if+ | bfTyName == naturalBF -> pure IntegerCata+ | bfTyName == negativeBF -> pure IntegerCata+ -- Note (Koz, 10/11/2025): This is hacky as hell, but since+ -- ByteString is technically a builtin type, this is the only+ -- way to spot its base functor.+ | bfTyName == "#ByteString" -> pure ByteStringCata+ | otherwise -> do+ datatypes <- asks (view #datatypeInfo)+ case Map.foldlWithKey' (go bfTyName) Nothing datatypes of+ Just (k, bbf) -> case tyVarInsts of+ NilV -> pure . RigidCata k $ bbf+ ConsV _ _ -> pure . NonRigidCata k . ThunkT $ bbf+ Nothing -> throwError . CataNoTypeForBaseFunctor $ bfTyName+ _ -> throwError . CataMonomorphicBaseFunctor $ bfTyName+ _ -> throwError . CataNotADatatypeBaseFunctor $ inputT+ _ -> throwError . CataNonRigidAlgebra $ t+ where+ go ::+ TyName ->+ Maybe (TyName, CompT AbstractTy) ->+ TyName ->+ DatatypeInfo AbstractTy ->+ Maybe (TyName, CompT AbstractTy)+ go targetTyName acc currTyName currTyInfo = case acc of+ Nothing -> case view #baseFunctor currTyInfo of+ Just (DataDeclaration name _ _ _, _) ->+ if name == targetTyName+ then case view #bbForm currTyInfo of+ Just (ThunkT bbfTy) -> Just (currTyName, bbfTy)+ _ -> acc -- technically impossible+ else acc+ _ -> acc+ _ -> acc+ -- Note (Koz, 11/11/2025): We need this procedure specifically for `cata`. The+ -- reason for this has to do with how we construct the 'base functor form' of+ -- the value to be torn down by the catamorphism, in order to use the+ -- unification machinery to get the type of the final result.+ --+ -- To be specific, suppose we have `<#List r (Maybe r) -> !Maybe r>` as our+ -- stated algebra type, with `r` rigid. Suppose also that the value to be torn+ -- down is `List r`. If we assume the rigid `r` is bound one scope away, `r`'s+ -- DeBruijn index will be different for each of these:+ --+ -- \* In the stated algebra type, `r`'s index will be `S (S Z)`; but+ -- \* In the value to be torn down, `r`'s index will be `S Z`.+ --+ -- As part of what we do here, we 'collect' the expected result of the+ -- catamorphism according to the stated algebra type. Then, we use a+ -- combination of the value to be torn down (or more precisely, its+ -- Boehm-Berrarducci form), together with the handlers, as arguments to the+ -- unifier to see what result we get on that basis. In theory, if the expected+ -- result type and the result of this unification agree, we type check.+ -- However, this would fail in our case: as the stated algebra type is+ -- `Comp0 $ Datatype "#List" [tyvar (S (S Z)) ix0, ...`, the expected result+ -- type would be `Datatype "Maybe" [tyvar (S (S Z) ix0]`. However, this is not+ -- valid in the scope of the value to be torn down: that same rigid would have+ -- the DeBruijn index `S Z` in that scope instead. This applies regardless of+ -- whether the tyvar is part of a datatype or not. This gives us an 'off by+ -- one' error.+ --+ -- As we prohibit non-rigid algebras, this requires us to lower the DeBruijn+ -- index by one for our process. This is, in fact, _why_ stated algebra+ -- types must be rigid: if they weren't, this process would become far more+ -- complicated, as we would now have to be careful to establish _which_+ -- tyvars need 'stepping down' and which don't!+ stepDown :: ValT AbstractTy -> ValT AbstractTy+ stepDown = \case+ Abstraction (BoundAt db i) -> case db of+ -- This is impossible, so we just return it unmodified+ Z -> Abstraction (BoundAt db i)+ (S db') -> Abstraction (BoundAt db' i)+ Datatype tyName tyArgs -> Datatype tyName . fmap stepDown $ tyArgs+ x -> x++fixArgType :: DeBruijn -> ValT AbstractTy -> ValT AbstractTy+fixArgType distance = \case+ Abstraction tyVar ->+ let tyVar' = addDeBruijn distance tyVar+ in Abstraction tyVar'+ ThunkT (CompN cnt (ArgsAndResult args res)) ->+ let args' = fmap (fixArgType distance) args+ res' = fixArgType distance res+ in ThunkT (CompN cnt (ArgsAndResult args' res'))+ bi@(BuiltinFlat {}) -> bi+ Datatype tn dtArgs -> Datatype tn $ fmap (fixArgType distance) dtArgs+ where+ addDeBruijn :: DeBruijn -> AbstractTy -> AbstractTy+ addDeBruijn toAdd (BoundAt db indx) =+ let db' = fromJust . preview asInt $ review asInt toAdd + review asInt db+ in BoundAt db' indx
src/Covenant/Data.hs view
@@ -36,6 +36,9 @@ hasRecursive, everythingOf, mapValT,++ -- ** Constants+ primBaseFunctorInfos, ) where @@ -43,10 +46,15 @@ import Control.Monad.Reader (MonadReader (ask, local), MonadTrans (lift), Reader, runReader) import Control.Monad.Trans.Except (ExceptT, runExceptT) import Covenant.DeBruijn (DeBruijn (S, Z), asInt)-import Covenant.Index (Count, Index, count0, intCount, intIndex)+import Covenant.Index (Count, Index, count0, intCount, intIndex, ix0) import Covenant.Internal.PrettyPrint (ScopeBoundary (ScopeBoundary))+import Covenant.Internal.Strategy+ ( DataEncoding (SOP),+ PlutusDataConstructor (PlutusB, PlutusConstr, PlutusI, PlutusList, PlutusMap),+ ) import Covenant.Internal.Type ( AbstractTy (BoundAt),+ BuiltinFlatT (ByteStringT, IntegerT), CompT (CompT), CompTBody (CompTBody), Constructor (Constructor),@@ -54,15 +62,36 @@ DataDeclaration (DataDeclaration, OpaqueData), TyName (TyName), ValT (Abstraction, BuiltinFlat, Datatype, ThunkT),+ byteStringBaseFunctor,+ naturalBaseFunctor,+ negativeBaseFunctor, )+import Covenant.Type+ ( CompT (Comp0, Comp1),+ CompTBody (ReturnT, (:--:>)),+ tyvar,+ ) import Data.Bitraversable (bisequence) import Data.Kind (Type)+import Data.Map.Strict (Map)+import Data.Map.Strict qualified as M import Data.Maybe (fromJust) import Data.Set (Set) import Data.Set qualified as Set import Data.Vector qualified as V import Data.Vector.NonEmpty qualified as NEV-import Optics.Core (A_Lens, LabelOptic (labelOptic), folded, lens, preview, review, toListOf, view, (%), _2)+import Optics.Core+ ( A_Lens,+ LabelOptic (labelOptic),+ folded,+ lens,+ preview,+ review,+ toListOf,+ view,+ (%),+ _2,+ ) import Optics.Indexed.Core (A_Fold) -- | All possible errors that could arise when constructing a Boehm-Berrarducci@@ -93,9 +122,12 @@ data DatatypeInfo (var :: Type) = DatatypeInfo { _originalDecl :: DataDeclaration var,+ -- The second element of the tuple here is the BB form of the Base functor+ -- (this is what we actually care about most of the time) _baseFunctorStuff :: Maybe (DataDeclaration var, ValT var), -- NOTE: The ONLY type that won't have a BB form is `Void` (or something isomorphic to it)- _bbForm :: Maybe (ValT var)+ _bbForm :: Maybe (ValT var),+ _isBaseFunctor :: Bool } deriving stock ( -- | @since 1.1.0@@ -114,8 +146,8 @@ {-# INLINEABLE labelOptic #-} labelOptic = lens- (\(DatatypeInfo ogDecl _ _) -> ogDecl)- (\(DatatypeInfo _ b c) ogDecl -> DatatypeInfo ogDecl b c)+ (\(DatatypeInfo ogDecl _ _ _) -> ogDecl)+ (\(DatatypeInfo _ b c d) ogDecl -> DatatypeInfo ogDecl b c d) -- | The base functor for this data type, if it exists. Types which are not -- self-recursive lack base functors.@@ -128,8 +160,8 @@ {-# INLINEABLE labelOptic #-} labelOptic = lens- (\(DatatypeInfo _ baseF _) -> baseF)- (\(DatatypeInfo a _ c) baseF -> DatatypeInfo a baseF c)+ (\(DatatypeInfo _ baseF _ _) -> baseF)+ (\(DatatypeInfo a _ c d) baseF -> DatatypeInfo a baseF c d) -- | The Boehm-Berrarducci form of this type, if it exists. Types with no -- constructors (that is, types without inhabitants) lack Boehm-Berrarducci@@ -143,8 +175,8 @@ {-# INLINEABLE labelOptic #-} labelOptic = lens- (\(DatatypeInfo _ _ bb) -> bb)- (\(DatatypeInfo a b _) bb -> DatatypeInfo a b bb)+ (\(DatatypeInfo _ _ bb _) -> bb)+ (\(DatatypeInfo a b _ d) bb -> DatatypeInfo a b bb d) -- | The base functor Boehm-Berrarducci form of this type, if it exists. A type -- must have both a base functor and a Boehm-Berrarducci form to have a base@@ -159,13 +191,42 @@ {-# INLINEABLE labelOptic #-} labelOptic = #baseFunctor % folded % _2 +-- | Is this the DatatypeInfo for a base functor?+-- @since 1.3.0+instance+ (k ~ A_Lens, a ~ Bool, b ~ Bool) =>+ LabelOptic "isBaseFunctor" k (DatatypeInfo var) (DatatypeInfo var) a b+ where+ {-# INLINEABLE labelOptic #-}+ labelOptic =+ lens+ (\(DatatypeInfo _ _ _ isbf) -> isbf)+ (\(DatatypeInfo a b c _) isbf -> DatatypeInfo a b c isbf)+ -- | Given a declaration of a datatype, either produce its datatype info, or--- fail.+-- fail. ----- @since 1.1.0-mkDatatypeInfo :: DataDeclaration AbstractTy -> Either BBFError (DatatypeInfo AbstractTy)-mkDatatypeInfo decl = DatatypeInfo decl <$> baseFStuff <*> mkBBF decl+-- Returns a map because it will bundle the base functor declaration for a given type+-- if a base functor can be generated.+-- @since 1.3.0+mkDatatypeInfo :: DataDeclaration AbstractTy -> Either BBFError (Map TyName (DatatypeInfo AbstractTy))+mkDatatypeInfo decl = do+ bbf <- mkBBF decl+ baseF <- baseFStuff+ case baseF of+ Nothing ->+ pure . M.singleton declTyName $ DatatypeInfo decl Nothing bbf False+ bf@(Just (baseFDecl, baseFBB)) -> do+ let baseFTyName = view #datatypeName baseFDecl+ baseFDatatypeInfo =+ M.singleton baseFTyName $+ DatatypeInfo baseFDecl Nothing (Just baseFBB) True+ parentDatatypeInfo =+ M.singleton declTyName $+ DatatypeInfo decl bf bbf False+ pure $ baseFDatatypeInfo <> parentDatatypeInfo where+ declTyName = view #datatypeName decl baseFStuff :: Either BBFError (Maybe (DataDeclaration AbstractTy, ValT AbstractTy)) baseFStuff = let baseFDecl = runReader (mkBaseFunctor decl) 0@@ -184,20 +245,25 @@ -- | Constructs a base functor from a suitable data declaration, returning -- 'Nothing' if the input is not a recursive type. ----- @since 1.1.0+-- Note that naming convention for base functors and their constructors gives "illegal" type names,+-- i.e. names that users could not choose themselves. For example, in:+-- @data List a = Nil | Cons a (List a)@+-- The type name for the generated base functor is '#List' and the constructors of the base functor are+-- '#Cons' and '#Nil'.+-- @since 1.3.0 mkBaseFunctor :: DataDeclaration AbstractTy -> Reader ScopeBoundary (Maybe (DataDeclaration AbstractTy)) mkBaseFunctor OpaqueData {} = pure Nothing-mkBaseFunctor (DataDeclaration tn numVars ctors strat) = do+mkBaseFunctor (DataDeclaration tn numVars ctors _) = do anyRecComponents <- or <$> traverse (hasRecursive tn) allCtorArgs if null ctors || not anyRecComponents then pure Nothing else do baseCtors <- traverse mkBaseCtor ctors- pure . Just $ DataDeclaration baseFName baseFNumVars baseCtors strat+ pure . Just $ DataDeclaration baseFName baseFNumVars baseCtors SOP where baseFName :: TyName baseFName = case tn of- TyName tyNameInner -> TyName (tyNameInner <> "_F")+ TyName tyNameInner -> TyName ("#" <> tyNameInner) baseFNumVars :: Count "tyvar" baseFNumVars = fromJust . preview intCount $ review intCount numVars + 1 -- The argument position of the new type variable parameter (typically `r`).@@ -228,7 +294,7 @@ mkBaseCtor (Constructor ctorNm ctorArgs) = Constructor (baseFCtorName ctorNm) <$> traverse replaceAllRecursive ctorArgs where baseFCtorName :: ConstructorName -> ConstructorName- baseFCtorName (ConstructorName nm) = ConstructorName (nm <> "_F")+ baseFCtorName (ConstructorName nm) = ConstructorName ("#" <> nm) allCtorArgs :: [ValT AbstractTy] allCtorArgs = concatMap (V.toList . view #constructorArgs) ctors -- This tells us whether the ValT *is* a recursive child of the parent type@@ -364,7 +430,34 @@ -- Only returns `Nothing` if there are no Constructors or the type is Opaque mkBBF' :: DataDeclaration AbstractTy -> ExceptT BBFError Maybe (ValT AbstractTy)-mkBBF' OpaqueData {} = lift Nothing+mkBBF' (OpaqueData _ ctorsSet) = do+ let bbfFunArgs = map mkOpaqueFn (Set.toList ctorsSet)+ case NEV.fromList bbfFunArgs of+ Nothing -> error "No ctors for opaque. If this happens it means we didn't run the kind checker."+ Just fn -> lift . Just . ThunkT . Comp1 . CompTBody $ NEV.snoc fn (tyvar Z ix0)+ where+ -- `r` as it appears in the thunks+ r :: ValT AbstractTy+ r = Abstraction (BoundAt (S Z) ix0)+ helper :: ValT AbstractTy -> ValT AbstractTy+ helper arg = ThunkT . Comp0 $ arg :--:> ReturnT r+ pList :: V.Vector (ValT AbstractTy) -> ValT AbstractTy+ pList = Datatype "List"+ pData :: ValT AbstractTy+ pData = Datatype "Data" mempty+ pPair :: ValT AbstractTy -> ValT AbstractTy -> ValT AbstractTy+ pPair a b = Datatype "Pair" $ V.fromList [a, b]+ mkOpaqueFn :: PlutusDataConstructor -> ValT AbstractTy+ mkOpaqueFn = \case+ PlutusI -> helper $ BuiltinFlat IntegerT+ PlutusB -> helper $ BuiltinFlat ByteStringT+ PlutusConstr ->+ ThunkT . Comp0 $+ BuiltinFlat IntegerT+ :--:> pList (V.fromList [pData])+ :--:> ReturnT r+ PlutusList -> helper (pList (V.singleton pData))+ PlutusMap -> helper (pList (V.singleton (pPair pData pData))) mkBBF' (DataDeclaration tn numVars ctors _) | V.null ctors = lift Nothing | otherwise = do@@ -414,5 +507,27 @@ they now occur within a Thunk), but after that bump everything is stable as indicated above. -} -{- Here for lack of a better place to put it (has to be available to Unification and ASG)+{- Primitive Base Functor Datatype Info++ This has to be here to avoid cyclic dependencies and we have to write them by hand.++ NOTE: THESE MUST BE INSERTED INTO THE DEFAULT ASGBUILDER CONTEXT WHEN IT IS CONSTRUCTED/INITIALIZED+ (it's not yet clear where the best place to do that will be) -}++primBaseFunctorInfos :: Map TyName (DatatypeInfo AbstractTy)+primBaseFunctorInfos =+ foldr+ ( ( \x acc ->+ let tnm = view (#originalDecl % #datatypeName) x+ in M.insert tnm x acc+ )+ . unsafeMkPrimInfo+ )+ M.empty+ [naturalBaseFunctor, negativeBaseFunctor, byteStringBaseFunctor]+ where+ unsafeMkPrimInfo :: DataDeclaration AbstractTy -> DatatypeInfo AbstractTy+ unsafeMkPrimInfo decl = case mkBBF decl of+ Left err -> error $ "Error constructing BBF for primitive base functor: " <> show err+ Right bbf -> DatatypeInfo decl Nothing bbf True
src/Covenant/Internal/KindCheck.hs view
@@ -72,6 +72,7 @@ | MutualRecursionDetected (Set TyName) | InvalidStrategy TyName | EncodingMismatch (EncodingArgErr AbstractTy)+ | OpaqueWithNoConstructors TyName deriving stock (Show, Eq) newtype KindCheckContext a = KindCheckContext (Map TyName (DataDeclaration a))@@ -118,7 +119,9 @@ checkDataDecls decls = runKindCheckM decls $ traverse_ checkDataDecl (M.elems decls) checkDataDecl :: DataDeclaration AbstractTy -> KindCheckM AbstractTy ()-checkDataDecl OpaqueData {} = pure ()+checkDataDecl (OpaqueData tn ctors)+ | null ctors = throwError $ OpaqueWithNoConstructors tn+ | otherwise = pure () checkDataDecl decl@(DataDeclaration tn _ ctors _) = do unless (checkStrategy decl) $ throwError (InvalidStrategy tn) cycleCheck' mempty decl
src/Covenant/Internal/Rename.hs view
@@ -310,11 +310,11 @@ -- REVIEW: I am not sure if we really want the scope arg to runRenameM to be `mempty`. -- If something breaks w/ BB forms or datatypes, look here. renameDatatypeInfo :: DatatypeInfo AbstractTy -> Either RenameError (DatatypeInfo Renamed)-renameDatatypeInfo (DatatypeInfo ogDecl baseFStuff bb) = runRenameM mempty $ do+renameDatatypeInfo (DatatypeInfo ogDecl baseFStuff bb isBF) = runRenameM mempty $ do ogDecl' <- renameDataDecl ogDecl baseFStuff' <- traverse (bitraverse renameDataDecl renameValT) baseFStuff bb' <- traverse renameValT bb- pure $ DatatypeInfo ogDecl' baseFStuff' bb'+ pure $ DatatypeInfo ogDecl' baseFStuff' bb' isBF -- A way of 'undoing' the renaming process. This is meant to be used only after -- applications, and assumes that what is being un-renamed is the result of a
src/Covenant/Internal/Strategy.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE GADTs #-}+ module Covenant.Internal.Strategy ( DataEncoding (..), PlutusDataStrategy (..),@@ -9,30 +11,11 @@ -- | Describes how a datatype is represented onchain. -- -- @since 1.1.0-data DataEncoding- = -- | The datatype is represented using the SOP primitives.- --- -- @since 1.1.0- SOP- | -- | The datatype is represented as @Data@, using the- -- specified strategy to determine specifics.- --- -- @since 1.1.0- PlutusData PlutusDataStrategy- | -- | The type uses one of the builtin \'special\' strategies. This- -- is used only for specific types and isn't generally available- -- for use.- --- -- @since 1.1.0- BuiltinStrategy InternalStrategy- deriving stock- ( -- | @since 1.1.0- Show,- -- | @since 1.1.0- Eq,- -- | @since 1.1.0- Ord- )+data DataEncoding where+ SOP :: DataEncoding+ PlutusData :: PlutusDataStrategy -> DataEncoding+ BuiltinStrategy :: InternalStrategy -> DataEncoding+ deriving stock (Show, Eq, Ord) -- NOTE: Wrapped data-primitive (Integer + ByteString) require a special case for their encoders, which was originally -- part of a "WrapperData" strategy here which we generalized to the NewtypeData strategy.
src/Covenant/Internal/Term.hs view
@@ -1,8 +1,8 @@ module Covenant.Internal.Term ( CovenantTypeError (..),- Id (..),+ Id (UnsafeMkId), typeId,- Arg (..),+ Arg (UnsafeMkArg), typeArg, Ref (..), typeRef,@@ -11,6 +11,7 @@ ASGNode (..), typeASGNode, ASGNodeType (..),+ BoundTyVar (..), ) where @@ -23,7 +24,6 @@ import Covenant.Internal.Rename (RenameError, UnRenameError) import Covenant.Internal.Type ( AbstractTy,- BuiltinFlatT, CompT, TyName, ValT,@@ -33,7 +33,10 @@ import Covenant.Type (ConstructorName, PlutusDataConstructor, Renamed) import Data.Kind (Type) import Data.Set qualified as Set+import Data.Text (Text) import Data.Vector (Vector)+import Data.Void (Void)+import Data.Wedge (Wedge) import Data.Word (Word64) -- | An error that can arise during the construction of an ASG by programmatic@@ -130,46 +133,6 @@ -- -- @since 1.1.0 EncodingError (EncodingArgErr AbstractTy)- | -- | The first argument to a catamorphism wasn't an algebra, as- -- it had the wrong arity.- --- -- @since 1.2.0- CataAlgebraWrongArity Int- | -- | The first argument to a catamorphism wasn't an algebra.- --- -- @since 1.1.0- CataNotAnAlgebra ASGNodeType- | -- | The second argument to a catamorphism wasn't a value type.- --- -- @since 1.1.0- CataApplyToNonValT ASGNodeType- | -- The algebra given to this catamorphism is not rigid (that is, its- -- computation type binds variables).- --- -- @since 1.2.0- CataNonRigidAlgebra (CompT AbstractTy)- | -- | The second argument to a catamorphism is a builtin type, but not one- -- we can eliminate with a catamorphism.- --- -- @since 1.1.0- CataWrongBuiltinType BuiltinFlatT- | -- | The second argument to a catamorphism is a value type, but not one we- -- can eliminate with a catamorphism. Usually, this means it's a variable.- --- -- @since 1.1.0- CataWrongValT (ValT AbstractTy)- | -- | We requested a catamorphism for a type that doesn't exist.- --- -- @since 1.2.0- CataNoSuchType TyName- | -- | We requested a catamorphism for a type without a base functor.- --- -- @since 1.2.0- CataNoBaseFunctorForType TyName- | -- | The provided algebra is not suitable for the given type.- --- -- @since 1.1.0- CataUnsuitable (CompT AbstractTy) (ValT AbstractTy) | -- | Someone attempted to construct a tyvar using a DB index or argument position -- which refers to a scope (or argument) that does not exist. --@@ -261,6 +224,114 @@ -- -- @since 1.2.0 MatchNoDatatypeInfo TyName+ | -- | We tried to get the base functor for a type in the ASG context, but the base functor does not exist.+ -- This can occur either because the type is not recursive and has no base functor, or because the type+ -- itself does not exist. It does not seem important to distinguish between the two failure cases.+ --+ -- @since 1.3.0+ BaseFunctorDoesNotExistFor TyName+ | -- | 'app' was called with a number of instantiation arguments that does not match the number of+ -- type variables bound in Count the CompT of the function to which arguments are being applied.+ -- The first Int is the number of bound tyvars in the function type, the second is the number of+ -- instantiations supplied.+ --+ -- @since 1.3.0+ WrongNumInstantiationsInApp (CompT Renamed) Int Int+ | -- | A miscellaneous error, needed to catch various things that can go wrong during datatype preparation and+ -- deserialization.+ --+ -- @since 1.3.0+ OtherError Text+ | -- | The stated algebra type for a catamorphism has an arity different to what we+ -- expected. Algebras must have an arity of 1.+ --+ -- @since 1.3.0+ CataWrongArity (CompT AbstractTy)+ | -- | The stated algebra type for a catamorphism has a different return type+ -- to what we expected. Algebras must have the same return type as the last+ -- type argument to the base functor.+ --+ -- The first field of this error is the type we expected, the second is the+ -- one we actually found.+ --+ -- @since 1.3.0+ CataWrongOutputType (ValT AbstractTy) (ValT AbstractTy)+ | -- | The stated algebra type's base functor does not correspond to any type+ -- known to us.+ --+ -- @since 1.3.0+ CataNoTypeForBaseFunctor TyName+ | -- | The stated algebra type's \'base functor\' is monomorphic.+ --+ -- @since 1.3.0+ CataMonomorphicBaseFunctor TyName+ | -- | The stated algebra type's \'base functor\' is not a datatype.+ --+ -- @since 1.3.0+ CataNotADatatypeBaseFunctor (ValT AbstractTy)+ | -- | The stated algebra type is not rigid: that is, it binds type variables.+ --+ -- @since 1.3.0+ CataNonRigidAlgebra (CompT AbstractTy)+ | -- | The structure to be torn down by a catamorphism is not suitable given+ -- the stated algebra type.+ --+ -- @since 1.3.0+ CataInvalidStructure (CompT AbstractTy) (ValT AbstractTy)+ | -- | The number of handlers provided to a catamorphism is wrong for the+ -- stated algebra type.+ --+ -- @since 1.3.0+ CataWrongNumberOfHandlers (CompT AbstractTy) (Vector Ref)+ | -- | The (internal) unification for a catamorphism failed. If you see this kind+ -- of error, it means that one of your handlers is not typed correctly. The+ -- exact unifier error is provided to help debug.+ --+ -- @since 1.3.0+ CataDidNotUnify (CompT AbstractTy) (Vector Ref) TypeAppError+ | -- | The (internal) renaming of the handlers for a catamorphism failed. If+ -- you see this kind of error, it means that one of your handlers is+ -- malformed. The exact renaming error is provided to help debug.+ --+ -- @since 1.3.0+ CataCouldNotRenameHandler (CompT AbstractTy) (Vector Ref) RenameError+ | -- | The (internal) Boehm-Berrarducci form of the structure to be torn down by+ -- a catamorphism did not rename correctly. If you see this error, this is a+ -- bug, so please report it!+ --+ -- @since 1.3.0+ CataCouldNotRenameBB (CompT AbstractTy) (Vector Ref) (CompT AbstractTy) RenameError+ | -- | A handler given to a catamorphism is not of a value type.+ --+ -- @since 1.3.0+ CataHandlerNotAValType ASGNodeType+ | -- | We could not rename one of the required substitutions into the (internal)+ -- Boehm-Berrarducci form of the structure to be torn down by a catamorphism+ -- over a polymorphic structure. If you see this error, this is a bug, so+ -- please report it!+ --+ -- @since 1.3.0+ CataCouldNotRenameSubstitutions (CompT AbstractTy) (Vector Ref) (CompT AbstractTy) (Vector (ValT AbstractTy)) RenameError+ | -- | We could not \'fix up\' the (internal) Boehm-Berrarducci form of the+ -- structure to be torn down by a catamorphism over a polymorphic structure+ -- after substitution. If you see this error, this is a bug, so please+ -- report it!+ --+ -- @since 1.3.0+ CataFixUpFailedForBB (CompT AbstractTy) (Vector Ref) (Vector (ValT AbstractTy)) (ValT Renamed) TypeAppError+ | -- | The argument given to a catamorphism to be torn down is not a value type.+ --+ -- @since 1.3.0+ CataNotAValueType ASGNodeType+ | -- | The expected result type of a catamorphism according to its stated algebra+ -- was not the same as what we actually got after applying handlers. If you+ -- see this error, it means that one or more of your handlers is not+ -- correct.+ --+ -- The first field is the type we expected, the second is what we got.+ --+ -- @since 1.3.0+ CataUnexpectedResultType (ValT AbstractTy) (ValT AbstractTy) deriving stock ( -- | @since 1.0.0 Eq,@@ -271,7 +342,17 @@ -- | A unique identifier for a node in a Covenant program. -- -- @since 1.0.0-newtype Id = Id Word64+newtype Id+ = -- | = Important note+ --+ -- Using this constructor is /not safe/. Do not do this unless you know+ -- /exactly/ what you are doing. We expose this constructor, in a limited way,+ -- to allow for certain kinds of testing, and /absolutely nothing else ever/.+ -- Attempts to use this in ways it was not designed to /will/ break, this+ -- interface is /not/ stable, and relying on it is /not/ a good plan.+ --+ -- @since 1.3.1+ UnsafeMkId Word64 deriving ( -- | @since 1.0.0 Eq,@@ -305,7 +386,17 @@ -- | An argument passed to a function in a Covenant program. -- -- @since 1.0.0-data Arg = Arg DeBruijn (Index "arg") (ValT AbstractTy)+data Arg+ = -- | = Important note+ --+ -- Using this constructor is /not safe/. Do not do this unless you know+ -- /exactly/ what you are doing. We expose this constructor, in a limited way,+ -- to allow for certain kinds of testing, and /absolutely nothing else ever/.+ -- Attempts to use this in ways it was not designed to /will/ break, this+ -- interface is /not/ stable, and relying on it is /not/ a good plan.+ --+ -- @since 1.3.1+ UnsafeMkArg DeBruijn (Index "arg") (ValT AbstractTy) deriving stock ( -- | @since 1.0.0 Eq,@@ -317,7 +408,7 @@ -- Helper to get the type of an argument. typeArg :: Arg -> ValT AbstractTy-typeArg (Arg _ _ t) = t+typeArg (UnsafeMkArg _ _ t) = t -- | A general reference in a Covenant program. --@@ -374,10 +465,14 @@ -- @since 1.0.0 data ValNodeInfo = LitInternal AConstant- | AppInternal Id (Vector Ref)+ | -- | The 'CompT' is the \'fully concretified\' function type: that is,+ -- all unifications with arguments have been done.+ --+ -- @since 1.3.0+ AppInternal Id (Vector Ref) (Vector (Wedge BoundTyVar (ValT Void))) (CompT AbstractTy) | ThunkInternal Id | -- | @since 1.1.0- CataInternal Ref Ref+ CataInternal (CompT AbstractTy) (Vector Ref) Ref | -- | @since 1.2.0 DataConstructorInternal TyName ConstructorName (Vector Ref) | -- | @since 1.2.0@@ -440,4 +535,17 @@ Ord, -- | @since 1.0.0 Show+ )++-- | Wrapper around an `Arg` that we know represents an in-scope type variable.+--+-- @since 1.2.0+data BoundTyVar = BoundTyVar DeBruijn (Index "tyvar")+ deriving stock+ ( -- @since 1.2.0+ Show,+ -- @since 1.2.0+ Eq,+ -- @since 1.2.0+ Ord )
src/Covenant/Internal/Type.hs view
@@ -207,6 +207,13 @@ -- | The name of a data type. This refers specifically to non-\'flat\' types -- either provided by the ledger, or defined by the user. --+-- User-defined 'TyName's must follow a set of naming rules, which will be checked. Specifically,+-- a 'TyName' must begin with a capital letter and consist only of alphanumeric characters and+-- underscores.+--+-- Compiler-generated 'TyName's are not bound by these conventions, and generated names for+-- base functors in particular use the naming convention of prefixing @#@ to the parent type.+-- -- @since 1.1.0 newtype TyName = TyName Text deriving@@ -451,30 +458,30 @@ _ -> False naturalBaseFunctor :: DataDeclaration AbstractTy-naturalBaseFunctor = DataDeclaration "Natural_F" count1 constrs SOP+naturalBaseFunctor = DataDeclaration "#Natural" count1 constrs SOP where constrs :: Vector (Constructor AbstractTy) constrs =- [ Constructor "ZeroNat_F" [],- Constructor "SuccNat_F" [Abstraction . BoundAt Z $ ix0]+ [ Constructor "#ZeroNat" [],+ Constructor "#SuccNat" [Abstraction . BoundAt Z $ ix0] ] negativeBaseFunctor :: DataDeclaration AbstractTy-negativeBaseFunctor = DataDeclaration "Negative_F" count1 constrs SOP+negativeBaseFunctor = DataDeclaration "#Negative" count1 constrs SOP where constrs :: Vector (Constructor AbstractTy) constrs =- [ Constructor "ZeroNeg_F" [],- Constructor "PredNeg_F" [Abstraction . BoundAt Z $ ix0]+ [ Constructor "#ZeroNeg" [],+ Constructor "#PredNeg" [Abstraction . BoundAt Z $ ix0] ] byteStringBaseFunctor :: DataDeclaration AbstractTy-byteStringBaseFunctor = DataDeclaration "ByteString_F" count1 constrs SOP+byteStringBaseFunctor = DataDeclaration "#ByteString" count1 constrs SOP where constrs :: Vector (Constructor AbstractTy) constrs =- [ Constructor "EmptyByteString_F" [],- Constructor "ConsByteString_F" [BuiltinFlat IntegerT, Abstraction . BoundAt Z $ ix0]+ [ Constructor "#EmptyByteString" [],+ Constructor "#ConsByteString" [BuiltinFlat IntegerT, Abstraction . BoundAt Z $ ix0] ] -- Helpers
src/Covenant/Internal/Unification.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE MultiWayIf #-} module Covenant.Internal.Unification ( TypeAppError (..),@@ -11,17 +10,20 @@ substitute, fixUp, reconcile,+ lookupDatatypeInfo,+ concretifyFT, ) where -import Control.Monad (foldM, unless, when)-import Data.Ord (comparing) #if __GLASGOW_HASKELL__==908 import Data.Foldable (foldl') #endif++import Control.Applicative (Alternative ((<|>)))+import Control.Monad (foldM, unless, when) import Control.Monad.Except (MonadError, catchError, throwError) import Control.Monad.Reader (MonadReader, ReaderT (runReaderT), ask)-import Covenant.Data (DatatypeInfo, mkDatatypeInfo)+import Covenant.Data (DatatypeInfo) import Covenant.Index (Index, intCount, intIndex) import Covenant.Internal.Rename (RenameError, renameDatatypeInfo) import Covenant.Internal.Type@@ -29,22 +31,22 @@ BuiltinFlatT, CompT (CompT), CompTBody (CompTBody),+ DataDeclaration (OpaqueData), Renamed (Rigid, Unifiable, Wildcard),- TyName (TyName),+ TyName, ValT (Abstraction, BuiltinFlat, Datatype, ThunkT),- byteStringBaseFunctor,- naturalBaseFunctor,- negativeBaseFunctor, )+import Covenant.Type (CompT (CompN), CompTBody (ArgsAndResult)) import Data.Kind (Type) import Data.Map (Map)+import Data.Map qualified as M import Data.Map.Merge.Strict qualified as Merge import Data.Map.Strict qualified as Map import Data.Maybe (fromJust, mapMaybe)+import Data.Ord (comparing) import Data.Set (Set) import Data.Set qualified as Set import Data.Text (Text)-import Data.Text qualified as Text import Data.Vector (Vector) import Data.Vector qualified as Vector import Data.Vector.NonEmpty (NonEmptyVector)@@ -114,42 +116,13 @@ lookupDatatypeInfo :: TyName -> UnifyM (DatatypeInfo Renamed)-lookupDatatypeInfo tn@(TyName rawTyName) =+lookupDatatypeInfo tn = ask >>= \tyDict -> case preview (ix tn) tyDict of- Nothing -> checkForBaseFunctor tyDict+ Nothing -> throwError . NoDatatypeInfo $ tn Just dti -> renamedToUnify . renameDatatypeInfo $ dti where- checkForBaseFunctor :: Map TyName (DatatypeInfo AbstractTy) -> UnifyM (DatatypeInfo Renamed)- checkForBaseFunctor tyDict = case Text.stripSuffix "_F" rawTyName of- Nothing -> throwError . NoDatatypeInfo $ tn- Just rawTyNameStub ->- if- -- Note (Koz, 12/08/2025): None of these specific cases should _ever_- -- fail. Thus, `fromRight` is safe here.- | rawTyNameStub == "Natural" ->- renamedToUnify . renameDatatypeInfo . fromRight . mkDatatypeInfo $ naturalBaseFunctor- | rawTyNameStub == "Negative" ->- renamedToUnify . renameDatatypeInfo . fromRight . mkDatatypeInfo $ negativeBaseFunctor- | rawTyNameStub == "ByteString" ->- renamedToUnify . renameDatatypeInfo . fromRight . mkDatatypeInfo $ byteStringBaseFunctor- -- We have something that _looks_ like a base functor, but not a- -- special builtin case. We thus need to ask the environment for the- -- recursive type it stands for, if it exists.- | otherwise -> do- let standinTyName = TyName rawTyNameStub- case preview (ix standinTyName) tyDict of- -- Now we have _truly_ missed.- Nothing -> throwError . NoDatatypeInfo $ tn- Just dti -> case view #baseFunctor dti of- Nothing -> throwError . NoDatatypeInfo $ tn- -- Since this is generated, it can't fail to rename- Just (bfDd, _) -> renamedToUnify . renameDatatypeInfo . fromRight . mkDatatypeInfo $ bfDd renamedToUnify :: Either RenameError (DatatypeInfo Renamed) -> UnifyM (DatatypeInfo Renamed) renamedToUnify = either (throwError . DatatypeInfoRenameFailed tn) pure- fromRight :: forall a b. (Show a) => Either a b -> b- fromRight = \case- Left err -> error . show $ err- Right x -> x lookupBBForm :: TyName -> UnifyM (ValT Renamed) lookupBBForm tn =@@ -157,6 +130,14 @@ Nothing -> throwError $ NoBBForm tn Just bbForm -> pure bbForm +-- Opaque types do not (and cannot) have a BB form, which breaks unification machinery that assumes all inhabiated types+-- have such a form. We need to branch on the "Opacity" of a type in `expectDatatype` and this lets us do that+isOpaqueType :: TyName -> UnifyM Bool+isOpaqueType tn =+ lookupDatatypeInfo tn >>= \dti -> case view #originalDecl dti of+ OpaqueData {} -> pure True+ _ -> pure False+ -- | Given information about in-scope datatypes, a computation type, and a list -- of arguments (some of which may be errors), try to construct the type of the -- result of the application of those arguments to the computation.@@ -180,7 +161,7 @@ when (numArgsActual < numArgsExpected) $ throwError $ InsufficientArgs numArgsActual f ys- when (numArgsExpected > numArgsActual) $+ when (numArgsActual > numArgsExpected) $ throwError $ ExcessArgs f (Vector.fromList ys) go curr (Vector.toList rest) ys@@ -365,18 +346,32 @@ -- the BB form using the arguments to the actual datatype. -- For example, the BB form of `Maybe` is: forall a r. r -> (a -> r) -> r -- which, if we concretify while attempting to unify with `Maybe Int`, becomes: `forall r. r -> (Int -> r) -> r`+ --+ -- Opaque datatypes are a special exception and are treated analogously to Builtins: They unify only with themselves,+ -- unifiables, or wildcards. expectDatatype tn args = do- bbForm <- lookupBBForm tn- bbFormConcreteE <- concretify bbForm args- case actual of- Abstraction (Rigid _ _) -> unificationError- Abstraction _ -> noSubUnify- Datatype tn' args'- | tn' /= tn -> unificationError- | otherwise -> do- bbFormConcreteA <- concretify bbForm args'- unify bbFormConcreteE bbFormConcreteA- _ -> unificationError+ isOpaqueType tn >>= \case+ False -> do+ bbForm <- lookupBBForm tn+ bbFormConcreteE <- concretify bbForm args+ case actual of+ Abstraction (Rigid _ _) -> unificationError+ Abstraction _ -> noSubUnify+ Datatype tn' args'+ | tn' /= tn -> unificationError+ | otherwise -> do+ bbFormConcreteA <- concretify bbForm args'+ unify bbFormConcreteE bbFormConcreteA+ _ -> unificationError+ True -> case actual of+ Abstraction Rigid {} -> unificationError+ Abstraction _ -> noSubUnify+ -- Opaque datatypes cannot be parameterized, so we only need to check the TyName+ Datatype tn' _args ->+ if tn == tn'+ then noSubUnify+ else unificationError+ _ -> unificationError concretify :: ValT Renamed -> Vector (ValT Renamed) -> UnifyM (ValT Renamed) concretify (ThunkT (CompT count (CompTBody fn))) args = fixUp $ ThunkT (CompT count (CompTBody newFn)) where@@ -414,3 +409,59 @@ _ -> case new of Abstraction (Unifiable _) -> pure old _ -> throwError $ CouldNotReconcile i old new++----- Extra stuff++concretifyFT ::+ CompT Renamed ->+ Vector (Maybe (ValT Renamed)) ->+ CompT Renamed+concretifyFT (CompN cnt (ArgsAndResult fromFn res)) fromArgs = unfixedResult+ where+ unfixedResult :: CompT Renamed+ unfixedResult = CompN cnt (ArgsAndResult subbedArgs subbedRes)++ subbedArgs = substMany allSubstitutions <$> fromFn+ subbedRes = substMany allSubstitutions res++ substMany :: [(Index "tyvar", ValT Renamed)] -> ValT Renamed -> ValT Renamed+ substMany subs val = foldl' (\acc (tv, ty) -> substitute tv ty acc) val subs++ allUnifiables = Set.toList $ Vector.foldMap collectUnifiables fromFn++ allSubstitutions = M.toList $ getInstantiations allUnifiables (Vector.toList fromFn) (Vector.toList fromArgs)++getInstantiations :: [Index "tyvar"] -> [ValT Renamed] -> [Maybe (ValT Renamed)] -> Map (Index "tyvar") (ValT Renamed)+getInstantiations [] _ _ = M.empty+getInstantiations _ [] _ = M.empty+getInstantiations _ _ [] = M.empty+getInstantiations vs (_ : fEs) (Nothing : aEs) = getInstantiations vs fEs aEs+getInstantiations (var : vars) fs@(fE : fEs) as@(aE' : aEs) =+ -- somewhat subjective but I think doing it w/ fromJust makes the logic easier to follow here+ let aE = fromJust aE'+ in case instantiates (Unifiable var) aE fE of+ Nothing -> getInstantiations [var] fEs aEs <> getInstantiations vars fs as+ Just t -> M.insert var t $ getInstantiations vars fs as++instantiates ::+ Renamed ->+ ValT Renamed -> -- the "more concrete type", usually the actual argument from 'app'+ ValT Renamed -> -- the "more polymorphic type', usually from the fn definition+ Maybe (ValT Renamed)+instantiates var concrete abstract = case (concrete, abstract) of+ (x, Abstraction a) -> if var == a then Just x else Nothing -- N.b. we need to be sure we only run this w/ unifiables as the first arg+ (ThunkT (CompN _ concreteFn), ThunkT (CompN _ abstractFn)) ->+ let concreteFn' = Vector.toList $ compTBodyToVec concreteFn+ abstractFn' = Vector.toList $ compTBodyToVec abstractFn+ in go concreteFn' abstractFn'+ (Datatype tnC argsC, Datatype tnA argsA)+ | tnC == tnA -> go (Vector.toList argsC) (Vector.toList argsA)+ _ -> Nothing+ where+ go :: [ValT Renamed] -> [ValT Renamed] -> Maybe (ValT Renamed)+ go [] _ = Nothing+ go _ [] = Nothing+ go (c : cs) (a : as) = instantiates var c a <|> go cs as++compTBodyToVec :: forall a. CompTBody a -> Vector (ValT a)+compTBodyToVec (ArgsAndResult args res) = Vector.snoc args res
+ src/Covenant/JSON.hs view
@@ -0,0 +1,1529 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE RankNTypes #-}++-- |+-- Module: Covenant.JSON+-- Copyright: (C) MLabs 2025+-- License: Apache 2.0+-- Maintainer: koz@mlabs.city, sean@mlabs.city+--+-- JSON serialization and deserialization utilities for the ASG.+--+-- = Note on Sum Type Encoding:+--+-- Unless otherwise noted, a Haskell sum type like:+--+-- @data Foo = Bar | Baz Int@+--+-- Is encoded to JSON using @{tag: \<CTOR NAME\>, fields: [\<Arg1\>, \<Arg2\>, \<ArgN\>]}@+--+-- This is used for all Haskell sum types which do /not/ have 'LabelOptic'+-- instnaces. For those with field names given by such instances, the @fields@+-- part of the encoded sum is not an array of arguments, but instead a JSON+-- object, with fields whose names correspond to the label optics. Comments make+-- it clear which types are encoded in which way.+--+-- @since 1.3.0+module Covenant.JSON+ ( -- * Serialization+ Version (..),+ SerializeErr (..),+ mkDatatypeInfos,+ compileAndSerialize,++ -- * Deserialization+ DeserializeErr (..),+ deserializeAndValidate,+ deserializeAndValidate_,+ )+where++#if __GLASGOW_HASKELL__==908+import Data.Foldable (foldl')+#endif+import Control.Exception (throwIO)+import Control.Monad (foldM, unless)+import Control.Monad.Error.Class (MonadError (throwError))+import Control.Monad.HashCons (MonadHashCons (lookupRef))+import Control.Monad.IO.Class (MonadIO (liftIO))+import Control.Monad.Reader (local)+import Control.Monad.Trans.Except (ExceptT, runExceptT)+import Covenant.ASG+ ( ASG (ASG),+ ASGBuilder,+ ASGNode,+ Arg,+ CompNodeInfo,+ CovenantError,+ Id,+ Ref,+ ValNodeInfo,+ app,+ builtin1,+ builtin2,+ builtin3,+ builtin6,+ cata,+ dataConstructor,+ err,+ force,+ lam,+ lit,+ match,+ runASGBuilder,+ thunk,+ )+import Covenant.Constant (AConstant (ABoolean, AByteString, AString, AUnit, AnInteger))+import Covenant.Data (DatatypeInfo, mkDatatypeInfo, primBaseFunctorInfos)+import Covenant.DeBruijn (DeBruijn, asInt)+import Covenant.Index (Count, Index, intCount, intIndex)+import Covenant.Internal.KindCheck (checkDataDecls)+import Covenant.Internal.Strategy+ ( InternalStrategy+ ( InternalAssocMapStrat,+ InternalListStrat,+ InternalOpaqueStrat,+ InternalPairStrat+ ),+ )+import Covenant.Internal.Term+ ( ASGNode (ACompNode, AValNode, AnError),+ Arg (UnsafeMkArg),+ BoundTyVar (BoundTyVar),+ CompNodeInfo+ ( Builtin1Internal,+ Builtin2Internal,+ Builtin3Internal,+ Builtin6Internal,+ ForceInternal,+ LamInternal+ ),+ CovenantTypeError (OtherError),+ Id (UnsafeMkId),+ Ref (AnArg, AnId),+ ValNodeInfo+ ( AppInternal,+ CataInternal,+ DataConstructorInternal,+ LitInternal,+ MatchInternal,+ ThunkInternal+ ),+ )+import Covenant.Internal.Type+ ( AbstractTy (BoundAt),+ CompT (CompT),+ CompTBody (CompTBody),+ ConstructorName (ConstructorName),+ DataDeclaration (OpaqueData),+ ValT (BuiltinFlat, ThunkT),+ )+import Covenant.Prim+ ( OneArgFunc+ ( BData,+ BLS12_381_G1_compress,+ BLS12_381_G1_neg,+ BLS12_381_G1_uncompress,+ BLS12_381_G2_compress,+ BLS12_381_G2_neg,+ BLS12_381_G2_uncompress,+ Blake2b_224,+ Blake2b_256,+ ComplementByteString,+ CountSetBits,+ DecodeUtf8,+ EncodeUtf8,+ FindFirstSetBit,+ FstPair,+ HeadList,+ IData,+ Keccak_256,+ LengthOfByteString,+ ListData,+ MapData,+ NullList,+ Ripemd_160,+ SerialiseData,+ Sha2_256,+ Sha3_256,+ SndPair,+ TailList,+ UnBData,+ UnConstrData,+ UnIData,+ UnListData,+ UnMapData+ ),+ SixArgFunc (ChooseData),+ ThreeArgFunc+ ( AndByteString,+ ChooseList,+ ExpModInteger,+ IfThenElse,+ IntegerToByteString,+ OrByteString,+ VerifyEcdsaSecp256k1Signature,+ VerifyEd25519Signature,+ VerifySchnorrSecp256k1Signature,+ WriteBits,+ XorByteString+ ),+ TwoArgFunc+ ( AddInteger,+ AppendByteString,+ AppendString,+ BLS12_381_G1_add,+ BLS12_381_G1_equal,+ BLS12_381_G1_hashToGroup,+ BLS12_381_G1_scalarMul,+ BLS12_381_G2_add,+ BLS12_381_G2_equal,+ BLS12_381_G2_hashToGroup,+ BLS12_381_G2_scalarMul,+ BLS12_381_finalVerify,+ BLS12_381_millerLoop,+ BLS12_381_mulMlResult,+ ByteStringToInteger,+ ChooseUnit,+ ConsByteString,+ ConstrData,+ DivideInteger,+ EqualsByteString,+ EqualsData,+ EqualsInteger,+ EqualsString,+ IndexByteString,+ LessThanByteString,+ LessThanEqualsByteString,+ LessThanEqualsInteger,+ LessThanInteger,+ MkCons,+ MkPairData,+ ModInteger,+ MultiplyInteger,+ QuotientInteger,+ ReadBit,+ RemainderInteger,+ ReplicateByte,+ RotateByteString,+ ShiftByteString,+ SubtractInteger,+ Trace+ ),+ )+import Covenant.Type+ ( BuiltinFlatT+ ( BLS12_381_G1_ElementT,+ BLS12_381_G2_ElementT,+ BLS12_381_MlResultT,+ BoolT,+ ByteStringT,+ IntegerT,+ StringT,+ UnitT+ ),+ Constructor (Constructor),+ DataDeclaration (DataDeclaration),+ DataEncoding (BuiltinStrategy, PlutusData, SOP),+ PlutusDataConstructor+ ( PlutusB,+ PlutusConstr,+ PlutusI,+ PlutusList,+ PlutusMap+ ),+ PlutusDataStrategy (EnumData, NewtypeData, ProductListData),+ TyName (TyName),+ ValT (Abstraction, Datatype),+ )+import Covenant.Type qualified as Ty+import Data.Aeson+ ( FromJSON (parseJSON),+ ToJSON (toEncoding),+ Value,+ eitherDecodeFileStrict,+ (.=),+ )+import Data.Aeson.Encoding+ ( Encoding,+ encodingToLazyByteString,+ int,+ list,+ pair,+ pairs,+ text,+ )+import Data.Aeson.KeyMap qualified as KM+import Data.Aeson.Types+ ( Array,+ Key,+ Object,+ Parser,+ Value (Object, String),+ withArray,+ withObject,+ withText,+ )+import Data.Bifunctor (Bifunctor (first))+import Data.ByteString (ByteString)+import Data.ByteString.Lazy qualified as BL+import Data.Char (isAlphaNum, isUpper)+import Data.Foldable (toList, traverse_)+import Data.Kind (Type)+import Data.Map (Map)+import Data.Map qualified as M+import Data.Maybe (fromJust)+import Data.Set qualified as S+import Data.Text (Text)+import Data.Text qualified as T+import Data.Vector (Vector)+import Data.Vector qualified as Vector+import Data.Vector.NonEmpty qualified as NEV+import Data.Void (Void, absurd)+import Data.Wedge (Wedge (Here, Nowhere, There))+import GHC.TypeLits (KnownSymbol, Symbol)+import Optics.Core (preview, review, set, view)+import Text.Hex qualified as Hex++-- | The errors that can arise from 'compileAndSerialize' not stemming from+-- 'IO'.+--+-- @since 1.3.0+data SerializeErr+ = -- | A datatype was specified in a way that isn't valid.+ DatatypeConversionFailure String+ | -- | The supplied ASG failed to compile.+ ASGCompilationFailure CovenantError+ deriving stock+ ( -- @since 1.3.0+ Show,+ -- @since 1.3.0+ Eq+ )++-- | Given a 'FilePath' to write output to, a collection of data declarations,+-- an 'ASGBuilder' and a version tag, compile the ASG, then write it to the+-- given file path in its JSON serialized form, together with the data types.+--+-- @since 1.3.0+compileAndSerialize ::+ forall (a :: Type).+ FilePath ->+ [DataDeclaration AbstractTy] ->+ ASGBuilder a ->+ Version ->+ ExceptT SerializeErr IO ()+compileAndSerialize path decls asgBuilder version = do+ case mkDatatypeInfos decls of+ Left err' -> throwError . DatatypeConversionFailure $ err'+ Right infos -> case runASGBuilder infos asgBuilder of+ Left err' -> throwError . ASGCompilationFailure $ err'+ Right (ASG asg) -> do+ let cu = CompilationUnit (Vector.fromList decls) asg version+ liftIO $ writeJSONWith path cu encodeCompilationUnit++-- | The errors that can arise from 'deserializeAndValidate' not stemming from+-- 'IO'.+--+-- @since 1.3.0+data DeserializeErr+ = -- | The serial form's JSON was not valid. This means that the given file+ -- cannot be an ASG.+ JSONParseFailure String+ | -- | The deserialized JSON corresponds to an ASG, but not a valid one.+ ASGValidationFail CovenantError+ deriving stock+ ( -- @since 1.3.0+ Show,+ -- @since 1.3.0+ Eq+ )++-- | Given a 'FilePath' to a serialized ASG, decode it if possible.+--+-- @since 1.3.0+deserializeAndValidate ::+ FilePath ->+ ExceptT DeserializeErr IO ASG+deserializeAndValidate path = do+ rawCU <- readJSON @CompilationUnit path+ case validateCompilationUnit rawCU of+ Left err' -> throwError . ASGValidationFail $ err'+ Right asg -> pure asg++-- | Like 'deserializeAndValidate' but runs directly in 'IO'.+--+-- = Note+--+-- This is mostly designed for use in tests, as it has no ability to \'trap\'+-- validation or deserialization errors. You most likely want+-- 'deserializeAndValidate'.+--+-- @since 1.3.0+deserializeAndValidate_ :: FilePath -> IO ASG+deserializeAndValidate_ path =+ runExceptT (deserializeAndValidate path) >>= either (throwIO . userError . show) pure++-- | Represents a Covenant version. This is currently just a tag, but may be+-- used in the future to enforce compatibility.+--+-- @since 1.3.0+data Version = Version {_major :: Int, _minor :: Int}+ deriving stock+ ( -- | @since 1.3.0+ Show,+ -- | @since 1.3.0+ Eq,+ -- | @since 1.3.0+ Ord+ )++data CompilationUnit+ = CompilationUnit+ { _datatypes :: Vector (DataDeclaration AbstractTy),+ _asg :: Map Id ASGNode,+ _version :: Version+ }+ deriving stock (Show, Eq)++-- NOTE: We run w/ an empty map because the declarations get inserted after they are kindchecked+validateCompilationUnit :: CompilationUnit -> Either CovenantError ASG+validateCompilationUnit = runASGBuilder M.empty . validateCompilationUnit'++validateCompilationUnit' :: CompilationUnit -> ASGBuilder ()+validateCompilationUnit' (CompilationUnit datatypes asg _) = do+ case mkDatatypeInfos (toList datatypes) of+ Left err' -> throwError $ OtherError (T.pack err')+ Right infos -> local (set #datatypeInfo infos) $ traverse_ go (M.toList asg)+ where+ go :: (Id, ASGNode) -> ASGBuilder ()+ go (parsedId, parsedNode) = case parsedNode of+ ACompNode compT compInfo -> case compInfo of+ Builtin1Internal bi1 -> checkNode "builtin1" (builtin1 bi1)+ Builtin2Internal bi2 -> checkNode "builtin2" (builtin2 bi2)+ Builtin3Internal bi3 -> checkNode "builtin3" (builtin3 bi3)+ Builtin6Internal bi6 -> checkNode "builtin6" (builtin6 bi6)+ LamInternal bodyRef -> checkNode "lam" $ lam compT (pure bodyRef)+ ForceInternal ref -> checkNode "force" $ force ref+ AValNode _ valInfo -> case valInfo of+ LitInternal aConstant -> checkNode "Lit" (lit aConstant)+ AppInternal fId argRefs instTys _ -> checkNode "App" (app fId argRefs instTys)+ ThunkInternal i -> checkNode "Thunk" (thunk i)+ CataInternal t r1 r2 -> checkNode "Cata" (cata t r1 r2)+ DataConstructorInternal tn cn args -> checkNode "DataConstructor" (dataConstructor tn cn args)+ MatchInternal scrut matcharms -> checkNode "Match" (match scrut matcharms)+ AnError -> checkNode "errorNode" err+ where+ checkNode :: String -> ASGBuilder Id -> ASGBuilder ()+ checkNode msg constructedId = do+ xid <- constructedId+ unless (parsedId == xid) $ error $ msg <> " id mismatch"+ lookupRef xid >>= \case+ Nothing -> error $ msg <> " node not found"+ Just asgNode ->+ unless (asgNode == parsedNode) $ do+ let errMsg =+ "unexpected "+ <> msg+ <> " node"+ <> "\n expected: "+ <> show parsedNode+ <> "\n actual: "+ <> show asgNode+ error errMsg++{- CompilationUnit++ Encodes as an object. The maps are represented by KV pairs in arrays. Example:++ {datatypes: [{k: "Maybe", v: ...}, {k: "Foo", v: ...}],+ asg: [{k: 0, v: ...}],+ version: {major: 1, minor: 2}+ }+-}++encodeCompilationUnit :: CompilationUnit -> Encoding+encodeCompilationUnit (CompilationUnit datatypes asg version) =+ pairs $+ pair "datatypes" (list encodeDataDeclarationAbstractTy . toList $ datatypes)+ <> pair "asg" (encodeMap encodeId encodeASGNode asg)+ <> pair "version" (encodeVersion version)++instance FromJSON CompilationUnit where+ parseJSON = withObject "CompilationUnit" $ \obj -> do+ datatypes <- lookupAndParse' obj "datatypes" $ withArray "datatype" $ \arr -> traverse decodeDataDeclarationAbstractTy arr+ asg <- lookupAndParse' obj "asg" $ decodeMap decodeId decodeASGNode+ version <- lookupAndParse' obj "version" decodeVersion+ pure $ CompilationUnit datatypes asg version++{- Version++ Serializes as an object with the fields you'd expect.++ Version 1 2+ ->+ {major: 1, minor: 2}++-}++-- | @since 1.3.0+encodeVersion :: Version -> Encoding+encodeVersion (Version major minor) = pairs ("major" .= major <> "minor" .= minor)++-- | @since 1.3.0+decodeVersion :: Value -> Parser Version+decodeVersion = withObject "Version" $ \obj -> do+ major <- withField "major" parseJSON obj+ minor <- withField "minor" parseJSON obj+ pure $ Version major minor++{- DataDeclaration & its components -}++{- Special handling to account for base functors (including "strange" base functors for Natural)++ {tyName: "Foo"}+ | {baseFunctorOf: "Foo"}+ | "NaturalBF" | "NegativeBF"+-}++-- | @since 1.3.0+encodeTyName :: TyName -> Encoding+encodeTyName (TyName tn) = case T.stripPrefix "#" tn of+ Nothing -> pairs ("tyName" .= tn)+ Just rootTypeName -> case rootTypeName of+ "Natural" -> text "NaturalBF"+ "Negative" -> text "NegativeBF"+ other -> pairs ("baseFunctorOf" .= other)++-- | The type name must conform with the type naming rules, i.e. it must+-- 1. Begin with a capital letter+-- 2. Consist only of alphanumeric characters and underscores+-- @since 1.3.0+decodeTyName :: Value -> Parser TyName+decodeTyName = \case+ String str -> case str of+ "NaturalBF" -> pure "#Natural"+ "NegativeBF" -> pure "#Negative"+ other -> fail $ "Expected 'NaturalBF' or 'NegativeBF' but got " <> T.unpack other+ Object km -> case KM.lookup "tyName" km of+ Nothing -> case KM.lookup "baseFunctorOf" km of+ Nothing -> fail "Received an object for TyName, but it didn't have any valid fields"+ Just rootType -> TyName . ("#" <>) <$> (parseJSON rootType >>= validateProperName)+ Just tn -> TyName <$> (parseJSON tn >>= validateProperName)+ other -> fail $ "Expected a String or Object for TyName, but got: " <> show other++validateProperName :: Text -> Parser Text+validateProperName nm+ | T.null nm = fail "Empty String cannot be a TyName or ConstructorName"+ | (isUpper (T.head nm) || T.head nm == '#') && T.all (\c -> isAlphaNum c || c == '_') nm = pure nm+ | otherwise = fail $ "Could not validate TyName or ConstructorName '" <> T.unpack nm <> "'"++{- Encodes as a simple JSON string, e.g.+ ConstructorName "Foo" -> "Foo"+-}++-- | @since 1.3.0+encodeConstructorName :: ConstructorName -> Encoding+encodeConstructorName (ConstructorName cn) = toEncoding cn++-- | The ctor name must conform with the ctor naming rules, i.e. it must+-- 1. Begin with a capital letter+-- 2. Consist only of alphanumeric characters and underscores+-- @since 1.3.0+decodeConstructorName :: Value -> Parser ConstructorName+decodeConstructorName = withText "ConstructorName" $ fmap ConstructorName . validateProperName++{- Encodes as an object. E.g.:++ Constructor "Just" [IntegerT]+ ->+ { constructorName: "Just"+ , constructorArgs: [...]}++-}++-- | @since 1.3.0+encodeConstructor :: Constructor AbstractTy -> Encoding+encodeConstructor (Constructor nm args) =+ let encodedArgs = list encodeValTAbstractTy $ Vector.toList args+ in pairs $+ pair "constructorName" (encodeConstructorName nm)+ <> pair "constructorArgs" encodedArgs++-- | @since 1.3.0+decodeConstructor :: Value -> Parser (Constructor AbstractTy)+decodeConstructor = withObject "Constructor" $ \obj -> do+ ctorNm <- lookupAndParse' obj "constructorName" decodeConstructorName+ ctorArgs <-+ lookupAndParse' obj "constructorArgs" $+ withArray "Constructor Args" (traverse decodeValTAbstractTy)+ pure $ Constructor ctorNm ctorArgs++{- DataEncoding encodes as a typical sum type, and will look like:++ {tag: "SOP", fields: []}+ | {tag: "PlutusData", fields: [...]}+ | {tag: "BuiltinStrategy", fields: [...]}+-}++-- | @since 1.3.0+encodeDataEncoding :: DataEncoding -> Encoding+encodeDataEncoding = \case+ SOP -> taggedFields "SOP" []+ PlutusData strat -> taggedFields "PlutusData" [encodePlutusDataStrategy strat]+ BuiltinStrategy internalStrat -> taggedFields "BuiltinStrategy" [encodeInternalStrategy internalStrat]++-- | @since 1.3.0+decodeDataEncoding :: Value -> Parser DataEncoding+decodeDataEncoding = withObject "DataEncoding" go+ where+ go :: Object -> Parser DataEncoding+ go obj = do+ tagStr <- lookupAndParse' obj "tag" (parseJSON @Text)+ fieldsArrVal <- lookupAndParse' obj "fields" pure+ mfield0 <- withArray "index 0" (\arr -> pure $ arr Vector.!? 0) fieldsArrVal+ case tagStr of+ "SOP" -> pure SOP+ otherTag -> case mfield0 of+ Nothing -> fail "No fields present when deserializing a PlutusData"+ Just field0 -> case otherTag of+ "PlutusData" -> PlutusData <$> decodePlutusDataStrategy field0+ "BuiltinStrategy" -> BuiltinStrategy <$> decodeInternalStrategy field0+ other -> fail $ "Invalid DataEncoding tag: " <> show other++{- PlutusDataStrategy encodes as a typical sum type. (Omitting the 'fields' field b/c it's an enumeration)++ {tag: "EnumData"}+ | {tag: "ProductListData"}+ | {tag: "ConstrData"}+ | {tag: "NewtypeData"}++-}++-- | @since 1.3.0+encodePlutusDataStrategy :: PlutusDataStrategy -> Encoding+encodePlutusDataStrategy = encodeEnum++-- | @since 1.3.0+decodePlutusDataStrategy :: Value -> Parser PlutusDataStrategy+decodePlutusDataStrategy =+ caseOnTag+ [ "EnumData" :=> constM EnumData,+ "ProductListData" :=> constM ProductListData,+ "ConstrData" :=> constM Ty.ConstrData,+ "NewtypeData" :=> constM NewtypeData+ ]++{- InternalStrategy encodes as a typical enumeration type:+ {tag: "InternalListStrat"}+ | {tag: "InternalPairStrat"}+ | {tag: "InternalDataStrat"}+ | {tag: "InternalAssocMapStrat"}+ | {tag: "InternalOpaqueStrat"}++-}+encodeInternalStrategy :: InternalStrategy -> Encoding+encodeInternalStrategy = encodeEnum++decodeInternalStrategy :: Value -> Parser InternalStrategy+decodeInternalStrategy =+ caseOnTag+ [ "InternalListStrat" :=> constM InternalListStrat,+ "InternalPairStrat" :=> constM InternalPairStrat,+ "InternalAssocMapStrat" :=> constM InternalAssocMapStrat,+ "InternalOpaqueStrat" :=> constM InternalOpaqueStrat+ ]++{- PlutusDataConstructor encodes as a typical enumeration type:++ {tag: "PlutusI"}+ | {tag: "PlutusB"}+ | {tag: "PlutusConstr"}+ | {tag: "PlutusList"}+ | {tag: PlutusMap}++-}++-- | @since 1.3.0+encodePlutusDataConstructor :: PlutusDataConstructor -> Encoding+encodePlutusDataConstructor = encodeEnum++-- | @since 1.3.0+decodePlutusDataConstructor :: Value -> Parser PlutusDataConstructor+decodePlutusDataConstructor =+ caseOnTag+ [ "PlutusI" :=> constM PlutusI,+ "PlutusB" :=> constM PlutusB,+ "PlutusConstr" :=> constM PlutusConstr,+ "PlutusList" :=> constM PlutusList,+ "PlutusMap" :=> constM PlutusMap+ ]++{- DataDeclaration AbstractTy is a bit atypical. It is a sum type, but we encode+ the arguments to the `DataDeclaration` constructor as an object instead of an array+ (to reduce the possibility for frontend errors).++ For example, if we have:++ @DataDeclaration "Maybe" (Count 1) [...Nothing...,...Just...] SOP@++ It will seralize like:++ {tag: "DataDeclaration"+ , fields: {+ datatypeName: "Maybe",+ datatypeBinders: 1,+ datatypeConstructors: [...],+ datatypeEncoding: {tag: "SOP"}+ }}++ For consistency, we do the same thing with Opaques. E.g.:++ @OpaqueData "Foo" [Plutus_I]@++ Will serialize to:++ { tag: "OpaqueData"+ , fields: {+ datatypeName: "Foo",+ opaquePlutusConstructors: [{tag: "Plutus_I"}]+ }}+-}++-- | @since 1.3.0+encodeDataDeclarationAbstractTy :: DataDeclaration AbstractTy -> Encoding+encodeDataDeclarationAbstractTy = \case+ DataDeclaration nm cnt ctors enc ->+ let fieldObj =+ pairs $+ pair "datatypeName" (encodeTyName nm)+ <> pair "datatypeBinders" (encodeCount cnt)+ <> pair "datatypeConstructors" (list encodeConstructor . Vector.toList $ ctors)+ <> pair "datatypeEncoding" (encodeDataEncoding enc)+ in pairs $ pair "tag" "DataDeclaration" <> pair "fields" fieldObj+ OpaqueData nm plutusCtors ->+ let fieldObj =+ pairs $+ pair "datatypeName" (encodeTyName nm)+ <> pair "opaquePlutusConstructors" (list encodePlutusDataConstructor . toList $ plutusCtors)+ in pairs $ pair "tag" "OpaqueData" <> pair "fields" fieldObj++-- | @since 1.3.0+decodeDataDeclarationAbstractTy :: Value -> Parser (DataDeclaration AbstractTy)+decodeDataDeclarationAbstractTy =+ caseOnTag+ [ "DataDeclaration" :=> goDataDecl,+ "OpaqueData" :=> goOpaqueData+ ]+ where+ goDataDecl :: Object -> Parser (DataDeclaration AbstractTy)+ goDataDecl obj = do+ fieldsObj <- lookupAndParse' obj "fields" (withObject "Datatype fields" pure)+ dtName <- lookupAndParse' fieldsObj "datatypeName" decodeTyName+ dtBinders <- lookupAndParse' fieldsObj "datatypeBinders" decodeCount+ dtCtors <- lookupAndParse' fieldsObj "datatypeConstructors" (withArray "Datatype ctors" (traverse decodeConstructor))+ dtEncoding <- lookupAndParse' fieldsObj "datatypeEncoding" decodeDataEncoding+ pure $ DataDeclaration dtName dtBinders dtCtors dtEncoding++ goOpaqueData :: Object -> Parser (DataDeclaration AbstractTy)+ goOpaqueData obj = do+ fieldsObj <- lookupAndParse' obj "fields" (withObject "Datatype fields (Opaque)" pure)+ dtName <- lookupAndParse' fieldsObj "datatypeName" decodeTyName+ plutusCtors <-+ lookupAndParse'+ fieldsObj+ "opaquePlutusConstructors"+ (withArray "Opaque Plutus Ctors" (traverse decodePlutusDataConstructor))+ pure $ OpaqueData dtName (S.fromList . Vector.toList $ plutusCtors)++{- ASG Specific Types & Components++-}++{- Id encodes directly as a number. E.g.:++ @Id 101@ -> 101+-}++-- | @since 1.3.0+encodeId :: Id -> Encoding+encodeId (UnsafeMkId n) = toEncoding n++-- | @since 1.3.0+decodeId :: Value -> Parser Id+decodeId = fmap UnsafeMkId . parseJSON++{- Ref encodes as a typical sum type without named fields:++ {tag: "AnArg", fields: [...]}+ | {tag: "AnId": fields: [101]}+-}++-- | @since 1.3.0+encodeRef :: Ref -> Encoding+encodeRef = \case+ AnArg arg' -> taggedFields "AnArg" [encodeArg arg']+ AnId i -> taggedFields "AnId" [encodeId i]++-- | @since 1.3.0+decodeRef :: Value -> Parser Ref+decodeRef =+ caseOnTag+ [ "AnArg" :=> withFields (withIndex 0 (fmap AnArg . decodeArg)),+ "AnId" :=> withFields (withIndex 0 (fmap AnId . decodeId))+ ]++{- Arg encodes as an object, e.g.:++ {argDeBruijn: 0,+ argIndex: 1,+ argType: ...+ }++-}++-- | @since 1.3.0+encodeArg :: Arg -> Encoding+encodeArg (UnsafeMkArg db ix ty) =+ let dbEnc = encodeDeBruijn db+ ixEnc = encodeIndex ix+ tyEnc = encodeValTAbstractTy ty+ in pairs $+ pair "argDeBruijn" dbEnc+ <> pair "argIndex" ixEnc+ <> pair "argType" tyEnc++-- | @since 1.3.0+decodeArg :: Value -> Parser Arg+decodeArg = withObject "Arg" $ \obj -> do+ argDB <- withField "argDeBruijn" decodeDeBruijn obj+ argIX <- withField "argIndex" decodeIndex obj+ argTy <- withField "argType" decodeValTAbstractTy obj+ pure $ UnsafeMkArg argDB argIX argTy++{- AConstant++ Serializes as a sum type without named fields:++ {tag: "AUnit"}+ | {tag: "ABoolean", fields: [true]}+ | {tag: "AnInteger", fields: [22]}+ | {tag: "AByteString", fields: ["\0x32"]}+ | {tag: "AString", fields: ["Hello"]}++-}++-- | @since 1.3.0+encodeAConstant :: AConstant -> Encoding+encodeAConstant = \case+ AUnit -> pairs $ pair "tag" "AUnit"+ ABoolean b -> taggedFields "ABoolean" [toEncoding b]+ AnInteger i -> taggedFields "AnInteger" [toEncoding i]+ AByteString bs -> taggedFields "AByteString" [toEncoding . Hex.encodeHex $ bs]+ AString str -> taggedFields "AString" [toEncoding str]++-- | @since 1.3.0+decodeAConstant :: Value -> Parser AConstant+decodeAConstant =+ caseOnTag+ [ "AUnit" :=> constM AUnit,+ "ABoolean" :=> withField0 (fmap ABoolean . parseJSON),+ "AnInteger" :=> withField0 (fmap AnInteger . parseJSON),+ "AByteString" :=> withField0 (fmap AByteString . decodeByteStringHex),+ "AString" :=> withField0 (fmap AString . parseJSON)+ ]++{- ValNodeInfo++ Serializes as a sum type without named fields:++ {tag: "Lit", fields: [a]}+ | {tag: "App", fields: [a,b,c,d]}+ | {tag: "Thunk",fields: [a]}+ | {tag: "Cata", fields: [a,b]}+ | {tag: "DataConstructor", fields: [a,b,c]}+ | {tag: "Match", fields: [a,b]}++-}++-- | @since 1.3.0+encodeValNodeInfo :: ValNodeInfo -> Encoding+encodeValNodeInfo = \case+ LitInternal aconst -> taggedFields "Lit" [encodeAConstant aconst]+ AppInternal f args instTys fTy ->+ taggedFields+ "App"+ [ encodeId f,+ list encodeRef . toList $ args,+ list encodeInstTy . toList $ instTys,+ encodeCompT encodeAbstractTy fTy+ ]+ ThunkInternal f -> taggedFields "Thunk" [encodeId f]+ CataInternal t handlers r2 -> taggedFields "Cata" [encodeCompT encodeAbstractTy t, list encodeRef . toList $ handlers, encodeRef r2]+ DataConstructorInternal tn cn args ->+ taggedFields+ "DataConstructor"+ [ encodeTyName tn,+ encodeConstructorName cn,+ list encodeRef . toList $ args+ ]+ MatchInternal scrut branches ->+ taggedFields "Match" [encodeRef scrut, list encodeRef . toList $ branches]++-- | @since 1.3.0+decodeValNodeInfo :: Value -> Parser ValNodeInfo+decodeValNodeInfo =+ caseOnTag+ [ "Lit" :=> withField0 (fmap LitInternal . decodeAConstant),+ "App"+ :=> withFields+ $ \fieldsArr -> do+ f <- withIndex 0 decodeId fieldsArr+ args <- withIndex 1 (withArray "App args" (traverse decodeRef)) fieldsArr+ instTys <- withIndex 2 (withArray "App instTys" (traverse decodeInstTy)) fieldsArr+ fTy <- withIndex 3 (decodeCompT decodeAbstractTy) fieldsArr+ pure $ AppInternal f args instTys fTy,+ "Thunk" :=> withField0 (fmap ThunkInternal . decodeId),+ "Cata"+ :=> withFields+ $ \fieldsArr -> do+ t <- withIndex 0 (decodeCompT decodeAbstractTy) fieldsArr+ handlers <- withIndex 1 (withArray "Cata arms" (traverse decodeRef)) fieldsArr+ r2 <- withIndex 2 decodeRef fieldsArr+ pure $ CataInternal t handlers r2,+ "DataConstructor"+ :=> withFields+ $ \fieldsArr -> do+ tn <- withIndex 0 decodeTyName fieldsArr+ ctorNm <- withIndex 1 decodeConstructorName fieldsArr+ args <- withIndex 2 (withArray "Datatype args" (traverse decodeRef)) fieldsArr+ pure $ DataConstructorInternal tn ctorNm args,+ "Match"+ :=> withFields+ $ \fieldsArr -> do+ scrut <- withIndex 0 decodeRef fieldsArr+ args <- withIndex 1 (withArray "Match branches" (traverse decodeRef)) fieldsArr+ pure $ MatchInternal scrut args+ ]++{- CompNodeInfo++ Serializes as a sum type without named fields:++ {tag: "Builtin1Internal", fields: [f]}+ | {tag: "Builtin2Internal", fields: [f]}+ | {tag: "Builtin3Internal", fields: [f]}+ | {tag: "Builtin6Internal", fields: [f]}+ | {tag: "LamInternal", fields: [r]}+ | {tag: "ForceInternal", fields: [r]}+-}++-- | @since 1.3.0+encodeCompNodeInfo :: CompNodeInfo -> Encoding+encodeCompNodeInfo = \case+ Builtin1Internal fun1 -> taggedFields "Builtin1Internal" [encodeOneArgFunc fun1]+ Builtin2Internal fun2 -> taggedFields "Builtin2Internal" [encodeTwoArgFunc fun2]+ Builtin3Internal fun3 -> taggedFields "Builtin3Internal" [encodeThreeArgFunc fun3]+ Builtin6Internal fun6 -> taggedFields "Builtin6Internal" [encodeSixArgFunc fun6]+ LamInternal f -> taggedFields "LamInternal" [encodeRef f]+ ForceInternal f -> taggedFields "ForceInternal" [encodeRef f]++-- | @since 1.3.0+decodeCompNodeInfo :: Value -> Parser CompNodeInfo+decodeCompNodeInfo =+ caseOnTag+ [ "Builtin1Internal" :=> withField0 (fmap Builtin1Internal . decodeOneArgFunc),+ "Builtin2Internal" :=> withField0 (fmap Builtin2Internal . decodeTwoArgFunc),+ "Builtin3Internal" :=> withField0 (fmap Builtin3Internal . decodeThreeArgFunc),+ "Builtin6Internal" :=> withField0 (fmap Builtin6Internal . decodeSixArgFunc),+ "LamInternal" :=> withField0 (fmap LamInternal . decodeRef),+ "ForceInternal" :=> withField0 (fmap ForceInternal . decodeRef)+ ]++{- ASGNode++ Serializes as a sum type without named fields:++ {tag: "ACompNode", fields: [ty,info]}+ | {tag: "AValNode", fields: [ty,info]}+ | {tag: "AnError"}++-}++-- | @since 1.3.0+encodeASGNode :: ASGNode -> Encoding+encodeASGNode = \case+ ACompNode compT compInfo -> taggedFields "ACompNode" [encodeCompT encodeAbstractTy compT, encodeCompNodeInfo compInfo]+ AValNode valT valInfo -> taggedFields "AValNode" [encodeValTAbstractTy valT, encodeValNodeInfo valInfo]+ AnError -> pairs $ pair "tag" "AnError"++-- | @since 1.3.0+decodeASGNode :: Value -> Parser ASGNode+decodeASGNode =+ caseOnTag+ [ "ACompNode" :=> withFields $ \fields -> do+ compT <- withIndex 0 (decodeCompT decodeAbstractTy) fields+ compInfo <- withIndex 1 decodeCompNodeInfo fields+ pure $ ACompNode compT compInfo,+ "AValNode" :=> withFields $ \fields -> do+ valT <- withIndex 0 decodeValTAbstractTy fields+ valInfo <- withIndex 1 decodeValNodeInfo fields+ pure $ AValNode valT valInfo,+ "AnError" :=> constM AnError+ ]++--+-- ValT, CompT & Friends/Components+--++{- DeBruijn++ Encodes directly as a number. E.g.++ @S Z@+ ->+ 1++-}++-- | @since 1.3.0+encodeDeBruijn :: DeBruijn -> Encoding+encodeDeBruijn = int . review asInt++-- | @since 1.3.0+decodeDeBruijn :: Value -> Parser DeBruijn+decodeDeBruijn v = do+ vRaw <- parseJSON @Int v+ if vRaw < 0+ then fail "Negative DeBruijn"+ else pure . fromJust . preview asInt $ vRaw++{- AbstractTy++ Standard product serialization as array:++ @BoundAt (S Z) ix0@+ ->+ [1,0]+-}++-- | @since 1.3.0+encodeAbstractTy :: AbstractTy -> Encoding+encodeAbstractTy (BoundAt db i) = list id [encodeDeBruijn db, encodeIndex i]++-- | @since 1.3.0+decodeAbstractTy :: Value -> Parser AbstractTy+decodeAbstractTy = withArray "AbstractTy" $ \arr -> do+ guardArrLen 2 arr+ db <- withIndex 0 decodeDeBruijn arr+ i <- withIndex 1 decodeIndex arr+ pure $ BoundAt db i++{- Count++ Serializes as a Number.++ @count0@+ ->+ 0++ count1+ ->+ 1+-}++-- | @since 1.3.0+encodeCount :: forall (s :: Symbol). Count s -> Encoding+encodeCount = int . review intCount++-- | @since 1.3.0+decodeCount :: forall (s :: Symbol). (KnownSymbol s) => Value -> Parser (Count s)+decodeCount v = do+ vRaw <- parseJSON @Int v+ if vRaw < 0+ then fail "Negative Count"+ else pure . fromJust . preview intCount $ vRaw++{- Index++ Serializes as a number. NOTE: Will require a type application for the decoder.++ ix0+ ->+ 0++ ix1+ ->+ 1+-}++-- | @since 1.3.0+encodeIndex :: forall (s :: Symbol). Index s -> Encoding+encodeIndex = int . review intIndex++-- | @since 1.3.0+decodeIndex :: forall (s :: Symbol). (KnownSymbol s) => Value -> Parser (Index s)+decodeIndex v = do+ vRaw <- parseJSON @Int v+ if vRaw < 0+ then fail "Negative Index"+ else pure . fromJust . preview intIndex $ vRaw++{- CompT AbstractTy++ Standard serialization as an array:++ @CompT count0 ...body...@+ ->+ [0,...encodedBody...]++-}++-- | @since 1.3.0+encodeCompT :: forall (a :: Type). (a -> Encoding) -> CompT a -> Encoding+encodeCompT fa (CompT cnt body) = list id [encodeCount cnt, encodeCompTBody fa body]++-- | @since 1.3.0+decodeCompT :: forall (a :: Type). (Value -> Parser a) -> Value -> Parser (CompT a)+decodeCompT fa = withArray "CompT" $ \arr -> do+ guardArrLen 2 arr+ cnt <- withIndex 0 decodeCount arr+ body <- withIndex 1 (decodeCompTBody fa) arr+ pure $ CompT cnt body++{- CompTBodyAbstractTy++ This is a newtype over a NonEmptyVector and so encodes directly as an array.++ @CompTBody [t1,t2,t3]@+ ->+ [encodedT1,encodedT2,encodedT3]++-}++-- | @since 1.3.0+encodeCompTBody :: forall (a :: Type). (a -> Encoding) -> CompTBody a -> Encoding+encodeCompTBody fa (CompTBody tys) = list (encodeValT fa) . toList $ tys++-- | @since 1.3.0+decodeCompTBody :: forall (a :: Type). (Value -> Parser a) -> Value -> Parser (CompTBody a)+decodeCompTBody fa = withArray "CompTBody" $ \arr -> do+ decodedBody <- NEV.fromVector <$> traverse (decodeValT fa) arr+ case decodedBody of+ Nothing -> fail "Empty vector of types in a CompTBody"+ Just res -> pure . CompTBody $ res++{- BuiltinFlatT++ Encodes as an enumeration (i.e. tag-only sum)++ {tag: "UnitT"}+ | {tag: "BoolT"}+ | {tag: "IntegerT"}+ | {tag: "StringT"}+ | {tag: "ByteStringT"}+ | {tag: "BLS12_381_G1_ElementT"}+ | {tag: "BLS12_381_G2_ElementT"}+ | {tag: "BLS12_381_MlResultT"}++-}++-- | @since 1.3.0+encodeBuiltinFlatT :: BuiltinFlatT -> Encoding+encodeBuiltinFlatT = encodeEnum++-- | @since 1.3.0+decodeBuiltinFlatT :: Value -> Parser BuiltinFlatT+decodeBuiltinFlatT =+ caseOnTag+ [ "UnitT" :=> constM UnitT,+ "BoolT" :=> constM BoolT,+ "IntegerT" :=> constM IntegerT,+ "StringT" :=> constM StringT,+ "ByteStringT" :=> constM ByteStringT,+ "BLS12_381_G1_ElementT" :=> constM BLS12_381_G1_ElementT,+ "BLS12_381_G2_ElementT" :=> constM BLS12_381_G2_ElementT,+ "BLS12_381_MlResultT" :=> constM BLS12_381_MlResultT+ ]++{- OneArgFunc++ Encodes as an enumeration (i.e. tag-only sum).++ The name of the tag literally matches the name of the constructor. (Too many to list)+-}++-- | @since 1.3.0+encodeOneArgFunc :: OneArgFunc -> Encoding+encodeOneArgFunc = encodeEnum++-- | @since 1.3.0+decodeOneArgFunc :: Value -> Parser OneArgFunc+decodeOneArgFunc =+ caseOnTag+ [ "LengthOfByteString" :=> constM LengthOfByteString,+ "Sha2_256" :=> constM Sha2_256,+ "Sha3_256" :=> constM Sha3_256,+ "Blake2b_256" :=> constM Blake2b_256,+ "EncodeUtf8" :=> constM EncodeUtf8,+ "DecodeUtf8" :=> constM DecodeUtf8,+ "FstPair" :=> constM FstPair,+ "SndPair" :=> constM SndPair,+ "HeadList" :=> constM HeadList,+ "TailList" :=> constM TailList,+ "NullList" :=> constM NullList,+ "MapData" :=> constM MapData,+ "ListData" :=> constM ListData,+ "IData" :=> constM IData,+ "BData" :=> constM BData,+ "UnConstrData" :=> constM UnConstrData,+ "UnMapData" :=> constM UnMapData,+ "UnListData" :=> constM UnListData,+ "UnIData" :=> constM UnIData,+ "UnBData" :=> constM UnBData,+ "SerialiseData" :=> constM SerialiseData,+ "BLS12_381_G1_neg" :=> constM BLS12_381_G1_neg,+ "BLS12_381_G1_compress" :=> constM BLS12_381_G1_compress,+ "BLS12_381_G1_uncompress" :=> constM BLS12_381_G1_uncompress,+ "BLS12_381_G2_neg" :=> constM BLS12_381_G2_neg,+ "BLS12_381_G2_compress" :=> constM BLS12_381_G2_compress,+ "BLS12_381_G2_uncompress" :=> constM BLS12_381_G2_uncompress,+ "Keccak_256" :=> constM Keccak_256,+ "Blake2b_224" :=> constM Blake2b_224,+ "ComplementByteString" :=> constM ComplementByteString,+ "CountSetBits" :=> constM CountSetBits,+ "FindFirstSetBit" :=> constM FindFirstSetBit,+ "Ripemd_160" :=> constM Ripemd_160+ ]++{- TwoArgFunc++ Encodes as an enumeration (i.e. tag-only sum).++ The name of the tag literally matches the name of the constructor. (Too many to list)+-}++-- | @since 1.3.0+encodeTwoArgFunc :: TwoArgFunc -> Encoding+encodeTwoArgFunc = encodeEnum++-- | @since 1.3.0+decodeTwoArgFunc :: Value -> Parser TwoArgFunc+decodeTwoArgFunc =+ caseOnTag+ [ "AddInteger" :=> constM AddInteger,+ "SubtractInteger" :=> constM SubtractInteger,+ "MultiplyInteger" :=> constM MultiplyInteger,+ "DivideInteger" :=> constM DivideInteger,+ "QuotientInteger" :=> constM QuotientInteger,+ "RemainderInteger" :=> constM RemainderInteger,+ "ModInteger" :=> constM ModInteger,+ "EqualsInteger" :=> constM EqualsInteger,+ "LessThanInteger" :=> constM LessThanInteger,+ "LessThanEqualsInteger" :=> constM LessThanEqualsInteger,+ "AppendByteString" :=> constM AppendByteString,+ "ConsByteString" :=> constM ConsByteString,+ "IndexByteString" :=> constM IndexByteString,+ "EqualsByteString" :=> constM EqualsByteString,+ "LessThanByteString" :=> constM LessThanByteString,+ "LessThanEqualsByteString" :=> constM LessThanEqualsByteString,+ "AppendString" :=> constM AppendString,+ "EqualsString" :=> constM EqualsString,+ "ChooseUnit" :=> constM ChooseUnit,+ "Trace" :=> constM Trace,+ "MkCons" :=> constM MkCons,+ "ConstrData" :=> constM ConstrData,+ "EqualsData" :=> constM EqualsData,+ "MkPairData" :=> constM MkPairData,+ "BLS12_381_G1_add" :=> constM BLS12_381_G1_add,+ "BLS12_381_G1_scalarMul" :=> constM BLS12_381_G1_scalarMul,+ "BLS12_381_G1_equal" :=> constM BLS12_381_G1_equal,+ "BLS12_381_G1_hashToGroup" :=> constM BLS12_381_G1_hashToGroup,+ "BLS12_381_G2_add" :=> constM BLS12_381_G2_add,+ "BLS12_381_G2_scalarMul" :=> constM BLS12_381_G2_scalarMul,+ "BLS12_381_G2_equal" :=> constM BLS12_381_G2_equal,+ "BLS12_381_G2_hashToGroup" :=> constM BLS12_381_G2_hashToGroup,+ "BLS12_381_millerLoop" :=> constM BLS12_381_millerLoop,+ "BLS12_381_mulMlResult" :=> constM BLS12_381_mulMlResult,+ "BLS12_381_finalVerify" :=> constM BLS12_381_finalVerify,+ "ByteStringToInteger" :=> constM ByteStringToInteger,+ "ReadBit" :=> constM ReadBit,+ "ReplicateByte" :=> constM ReplicateByte,+ "ShiftByteString" :=> constM ShiftByteString,+ "RotateByteString" :=> constM RotateByteString+ ]++{- ThreeArgFunc++ Encodes as an enumeration (i.e. tag-only sum).++ The name of the tag literally matches the name of the constructor. (Too many to list)+-}++-- | @since 1.3.0+encodeThreeArgFunc :: ThreeArgFunc -> Encoding+encodeThreeArgFunc = encodeEnum++-- | @since 1.3.0+decodeThreeArgFunc :: Value -> Parser ThreeArgFunc+decodeThreeArgFunc =+ caseOnTag+ [ "VerifyEd25519Signature" :=> constM VerifyEd25519Signature,+ "VerifyEcdsaSecp256k1Signature" :=> constM VerifyEcdsaSecp256k1Signature,+ "VerifySchnorrSecp256k1Signature" :=> constM VerifySchnorrSecp256k1Signature,+ "IfThenElse" :=> constM IfThenElse,+ "ChooseList" :=> constM ChooseList,+ "IntegerToByteString" :=> constM IntegerToByteString,+ "AndByteString" :=> constM AndByteString,+ "OrByteString" :=> constM OrByteString,+ "XorByteString" :=> constM XorByteString,+ "WriteBits" :=> constM WriteBits,+ "ExpModInteger" :=> constM ExpModInteger+ ]++{- SixArgFunc++ Encodes as an enumeration (i.e. tag-only sum).++ The name of the tag literally matches the name of the constructor. (Too many to list)+-}++-- | @since 1.3.0+encodeSixArgFunc :: SixArgFunc -> Encoding+encodeSixArgFunc = encodeEnum++-- | @since 1.3.0+decodeSixArgFunc :: Value -> Parser SixArgFunc+decodeSixArgFunc =+ caseOnTag+ [ "ChooseData" :=> constM ChooseData+ ]++{- ValT++ Encodes as a tagged sum without explicit field names:++ {tag: "Abstraction", fields: [...]}+ | {tag: "ThunkT", fields: [...]}+ | {tag: "BuiltinFlat", fields: : [...]}+ | {tag: "Datatype", fields: [...]}++-}++encodeValT :: forall (a :: Type). (a -> Encoding) -> ValT a -> Encoding+encodeValT fa = \case+ Abstraction x -> taggedFields "Abstraction" [fa x]+ ThunkT compT -> taggedFields "ThunkT" [encodeCompT fa compT]+ BuiltinFlat biFlat -> taggedFields "BuiltinFlat" [encodeBuiltinFlatT biFlat]+ Datatype tn args -> taggedFields "Datatype" [encodeTyName tn, list (encodeValT fa) . toList $ args]++decodeValT :: forall (a :: Type). (Value -> Parser a) -> Value -> Parser (ValT a)+decodeValT fa =+ caseOnTag+ [ "Abstraction" :=> withField0 (fmap Abstraction . fa),+ "ThunkT" :=> withField0 (fmap ThunkT . decodeCompT fa),+ "BuiltinFlat" :=> withField0 (fmap BuiltinFlat . decodeBuiltinFlatT),+ "Datatype" :=> withFields $ \arr -> do+ tn <- withIndex 0 decodeTyName arr+ ctors <- withIndex 1 (withArray "datatype args" (traverse (decodeValT fa))) arr+ pure $ Datatype tn ctors+ ]++{- Encodes as an array [DeBruijn, Index "tyvar"]+-}+encodeBoundTyVar :: BoundTyVar -> Encoding+encodeBoundTyVar (BoundTyVar db ix) = list id [encodeDeBruijn db, encodeIndex ix]++decodeBoundTyVar :: Value -> Parser BoundTyVar+decodeBoundTyVar = withArray "BoundTyVar" $ \arr -> do+ db <- withIndex 0 decodeDeBruijn arr+ ix <- withIndex 1 decodeIndex arr+ pure $ BoundTyVar db ix++{- Encoding is fully determined by other functions+-}+encodeInstTy :: Wedge BoundTyVar (ValT Void) -> Encoding+encodeInstTy = encodeWedge encodeBoundTyVar (encodeValT encodeVoid)++decodeInstTy :: Value -> Parser (Wedge BoundTyVar (ValT Void))+decodeInstTy = decodeWedge decodeBoundTyVar (decodeValT decodeVoid)++-- | @since 1.3.0+encodeValTAbstractTy :: ValT AbstractTy -> Encoding+encodeValTAbstractTy = encodeValT encodeAbstractTy++-- | @since 1.3.0+decodeValTAbstractTy :: Value -> Parser (ValT AbstractTy)+decodeValTAbstractTy = decodeValT decodeAbstractTy++-- Helpers++encodeVoid :: Void -> Encoding+encodeVoid = absurd++decodeVoid :: Value -> Parser Void+decodeVoid _ = fail "Void isn't inhabited, you can't decode a value to it"++{- We encode maps as arrays of {key: k, value: v} pairs+-}+encodeMap :: forall k v. (k -> Encoding) -> (v -> Encoding) -> Map k v -> Encoding+encodeMap fk fv m =+ list id $+ M.foldlWithKey'+ ( \acc k v ->+ let entry = pairs $ pair "key" (fk k) <> pair "value" (fv v)+ in entry : acc+ )+ []+ m++decodeMap ::+ forall k v.+ (Ord k) =>+ (Value -> Parser k) ->+ (Value -> Parser v) ->+ Value ->+ Parser (Map k v)+decodeMap fk fv = withArray "Map" $ \arr ->+ foldM+ ( \acc x -> flip (withObject "kvPair") x $ \obj -> do+ kfield <- lookupAndParse' obj "key" fk+ vfield <- lookupAndParse' obj "value" fv+ pure $ M.insert kfield vfield acc+ )+ M.empty+ arr++{- We encode wedges as a normal sum type, a la:++ {tag: "Nowhere"}+ | {tag: "Here", fields: [a]}+ | {tag: "There", fields: [b]}+-}+encodeWedge ::+ forall (a :: Type) (b :: Type).+ (a -> Encoding) ->+ (b -> Encoding) ->+ Wedge a b ->+ Encoding+encodeWedge fa fb = \case+ Nowhere -> pairs $ pair "tag" "Nowhere"+ Here a -> taggedFields "Here" [fa a]+ There b -> taggedFields "There" [fb b]++decodeWedge ::+ forall (a :: Type) (b :: Type).+ (Value -> Parser a) ->+ (Value -> Parser b) ->+ Value ->+ Parser (Wedge a b)+decodeWedge fa fb =+ caseOnTag+ [ "Nowhere" :=> constM Nowhere,+ "Here" :=> fmap Here . withField0 fa,+ "There" :=> fmap There . withField0 fb+ ]++-- Mainly for readability/custom fixity, effectively (,)+data (:=>) a b = a :=> b++infixr 0 :=>++-- Simulated pattern matching on the `tag` field of an object. Will throw an error if the+-- value is not an object. This is a convenience function, and it is *very* convenient.+caseOnTag :: forall (a :: Type). [Text :=> (Object -> Parser a)] -> Value -> Parser a+caseOnTag xs = withObject "CaseOnTag" go+ where+ go :: Object -> Parser a+ go obj = do+ let caseDict = foldl' (\acc (t :=> fn) -> M.insert t fn acc) M.empty xs+ tagVal <- lookupAndParse' obj "tag" (parseJSON @Text)+ case M.lookup tagVal caseDict of+ Just f -> f obj+ Nothing -> fail $ "Expected a tagged object with one of the tags: " <> show (M.keys caseDict) <> " but got " <> show obj++-- Stupid helper to avoid have to type `\_ -> pure x` a million times in `caseOnTag` matches+constM :: forall (f :: Type -> Type) (a :: Type). (Applicative f) => a -> (forall (b :: Type). b -> f a)+constM x _ = pure x++guardArrLen :: Int -> Array -> Parser ()+guardArrLen expectedLen arr+ | Vector.length arr == expectedLen = pure ()+ | otherwise =+ fail $+ "Expected an array with "+ <> show expectedLen+ <> " elements, but got one with "+ <> show (Vector.length arr)+ <> " elements"++-- Do something with the array at the tag "fields" in an object. Convenience helper.+withFields :: forall (a :: Type). (Array -> Parser a) -> Object -> Parser a+withFields f obj = lookupAndParse' obj "fields" $ \arrVal -> withArray "field array" f arrVal++-- Do something with the element at a given index in a JSON array.+withIndex :: forall (a :: Type). Int -> (Value -> Parser a) -> Array -> Parser a+withIndex i f arr = case arr Vector.!? i of+ Nothing -> fail $ "No element at index " <> show i <> " found in array " <> show arr+ Just elemAtIx -> f elemAtIx++-- flipped variant of lookupAndParse', for point free functions+withField :: forall (a :: Type). Key -> (Value -> Parser a) -> Object -> Parser a+withField k f obj = lookupAndParse' obj k f++-- A lot of our sums have a "fields" object with only one element, this saves us a bit of repetition for that common case.+-- Because this is intended to be used with either `withObject` or `caseOnTag`, it takes an Object which is expected to have a+-- "fields" fieldName with an array+withField0 :: forall (a :: Type). (Value -> Parser a) -> Object -> Parser a+withField0 f = withFields (\arr -> guardArrLen 1 arr >> withIndex 0 f arr)++-- Lookup the key in an object and apply the given monadic function to the value you get.+lookupAndParse' :: forall (a :: Type). Object -> Key -> (Value -> Parser a) -> Parser a+lookupAndParse' obj k f = case KM.lookup k obj of+ Nothing -> fail $ "No key '" <> show k <> "' found in object"+ Just v -> f v++-- NOTE: Must *ONLY* be used on *true* Enums, i.e. sum types with only 0-argument constructors+encodeEnum :: forall (a :: Type). (Show a) => a -> Encoding+encodeEnum = pairs . ("tag" .=) . show++-- Helper for constructing sum type Encodings.+-- 'taggedFields "name" [f1,f2,f3]' generates '{tag: "name", fields: [f1,f2,f3]}+taggedFields :: Text -> [Encoding] -> Encoding+taggedFields tg fieldArgs = pairs $ "tag" .= tg <> pair "fields" (list id fieldArgs)++-- Decodes a hex encoded bytestring+decodeByteStringHex :: Value -> Parser ByteString+decodeByteStringHex = withText "ByteString (Hex Encoded)" $ \txt -> case Hex.decodeHex txt of+ Nothing -> fail $ "Failed to decode hex bytestring: " <> show txt+ Just bs -> pure bs++-- | Given a collection of datatype declarations, convert them to+-- 'DatatypeInfo's by generating their base functor and Boehm-Berrarducci+-- encodings. Then add to these all the base functors for the built-in types.+--+-- @since 1.3.0+mkDatatypeInfos ::+ [DataDeclaration AbstractTy] ->+ Either String (Map TyName (DatatypeInfo AbstractTy))+mkDatatypeInfos decls = do+ let tyDict = foldl' (\acc x -> M.insert (view #datatypeName x) x acc) M.empty decls+ case checkDataDecls tyDict of+ Left kcErr -> Left $ "KindCheck error: " <> show kcErr+ Right _ ->+ first (("DatatypeInfo error: " <>) . show) $+ foldl'+ (\acc decl -> (<>) <$> mkDatatypeInfo decl <*> acc)+ (Right primBaseFunctorInfos)+ tyDict++-- IO Helpers++writeJSONWith :: forall (a :: Type). FilePath -> a -> (a -> Encoding) -> IO ()+writeJSONWith path x f = BL.writeFile path (encodingToLazyByteString . f $ x)++readJSON :: forall (a :: Type). (FromJSON a) => FilePath -> ExceptT DeserializeErr IO a+readJSON path =+ liftIO (eitherDecodeFileStrict @a path) >>= \case+ Left err' -> throwError . JSONParseFailure $ err'+ Right res -> pure res
src/Covenant/Prim.hs view
@@ -20,13 +20,13 @@ ) where -import Covenant.DeBruijn (DeBruijn (S, Z))+import Covenant.DeBruijn (DeBruijn (Z)) import Covenant.Index (ix0, ix1) import Covenant.Type ( AbstractTy, CompT (Comp0, Comp1, Comp2), CompTBody (ReturnT, (:--:>)),- ValT (ThunkT),+ ValT, boolT, byteStringT, dataType1T,@@ -366,8 +366,6 @@ | IfThenElse | -- | @since 1.1.0 ChooseList- | -- | @since 1.1.0- CaseList | IntegerToByteString | AndByteString | OrByteString@@ -395,7 +393,6 @@ VerifySchnorrSecp256k1Signature, IfThenElse, ChooseList,- CaseList, IntegerToByteString, AndByteString, OrByteString,@@ -414,12 +411,6 @@ VerifySchnorrSecp256k1Signature -> signatureT IfThenElse -> Comp1 $ boolT :--:> aT :--:> aT :--:> ReturnT aT ChooseList -> Comp2 $ listT aT :--:> bT :--:> bT :--:> ReturnT bT- CaseList ->- Comp2 $- bT- :--:> ThunkT (Comp0 $ aTOuter :--:> listT aTOuter :--:> ReturnT bTOuter)- :--:> listT aT- :--:> ReturnT bT IntegerToByteString -> Comp0 $ boolT :--:> integerT :--:> integerT :--:> ReturnT byteStringT@@ -454,9 +445,7 @@ -- | All six-argument primitives provided by Plutus. -- -- @since 1.1.0-data SixArgFunc- = ChooseData- | CaseData+data SixArgFunc = ChooseData deriving stock ( -- | @since 1.0.0 Eq,@@ -471,7 +460,7 @@ -- @since 1.1.0 instance Arbitrary SixArgFunc where {-# INLINEABLE arbitrary #-}- arbitrary = elements [ChooseData, CaseData]+ arbitrary = pure ChooseData -- | Produce the type of a six-argument primop. --@@ -487,15 +476,6 @@ :--:> aT :--:> aT :--:> ReturnT aT- CaseData ->- Comp1 $- ThunkT (Comp0 $ integerT :--:> listT dataT :--:> ReturnT aTOuter)- :--:> ThunkT (Comp0 $ listT (pairT dataT dataT) :--:> ReturnT aTOuter)- :--:> ThunkT (Comp0 $ listT dataT :--:> ReturnT aTOuter)- :--:> ThunkT (Comp0 $ integerT :--:> ReturnT aTOuter)- :--:> ThunkT (Comp0 $ byteStringT :--:> ReturnT aTOuter)- :--:> dataT- :--:> ReturnT aT -- Helpers @@ -511,11 +491,5 @@ aT :: ValT AbstractTy aT = tyvar Z ix0 -aTOuter :: ValT AbstractTy-aTOuter = tyvar (S Z) ix0- bT :: ValT AbstractTy bT = tyvar Z ix1--bTOuter :: ValT AbstractTy-bTOuter = tyvar (S Z) ix1
src/Covenant/Test.hs view
@@ -1,6 +1,9 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedLists #-} {-# LANGUAGE PolyKinds #-} +{- HLINT ignore "Use camelCase" -}+ -- | -- Module: Covenant.Test -- Copyright: (C) MLabs 2025@@ -32,12 +35,15 @@ -- ** Test helpers checkApp,+ conformanceDatatypes1,+ conformanceDatatypes2, failLeft, tyAppTestDatatypes, list, tree, weirderList, unsafeTyCon,+ unsafeMkDatatypeInfos, -- ** Datatype checks cycleCheck,@@ -63,6 +69,12 @@ DebugASGBuilder (..), debugASGBuilder, typeIdTest,+ Arg (UnsafeMkArg),+ Id (UnsafeMkId),++ -- ** Exports for codegen tests+ concretifyMinimalBuilder,+ concretifyMegaTest, ) where @@ -83,23 +95,16 @@ ) import Control.Monad.Trans (MonadTrans (lift)) import Control.Monad.Trans.Except (ExceptT, runExceptT)-import Covenant.ASG (ASGEnv (ASGEnv), ASGNode, CovenantError (TypeError), CovenantTypeError, Id, ScopeInfo (ScopeInfo))+import Covenant.ASG (ASGBuilder, ASGEnv (ASGEnv), ASGNode, CovenantError (TypeError), CovenantTypeError, Id, Ref (AnArg, AnId), ScopeInfo (ScopeInfo), app', arg, boundTyVar, builtin2, builtin3, ctor, ctor', dtype, force, lam, lazyLam, lit, match, thunk)+import Covenant.Constant (AConstant (ABoolean, AnInteger)) import Covenant.Data ( DatatypeInfo, mkDatatypeInfo, noPhantomTyVars,- )-import Covenant.DeBruijn (DeBruijn (Z), asInt)-import Covenant.Index- ( Count,- count0,- count1,- count2,- intCount,- intIndex,- ix0,- ix1,+ primBaseFunctorInfos, )+import Covenant.DeBruijn (DeBruijn (S, Z), asInt)+import Covenant.Index (Count, Index, count0, count1, count2, intCount, intIndex, ix0, ix1, ix2, ix3) import Covenant.Internal.KindCheck ( checkDataDecls, checkEncodingArgs,@@ -127,9 +132,15 @@ ) import Covenant.Internal.Strategy ( DataEncoding (PlutusData, SOP),+ PlutusDataConstructor (PlutusB, PlutusI), PlutusDataStrategy (ConstrData), )-import Covenant.Internal.Term (ASGNodeType (CompNodeType, ValNodeType), typeId)+import Covenant.Internal.Term+ ( ASGNodeType (CompNodeType, ValNodeType),+ Arg (UnsafeMkArg),+ Id (UnsafeMkId),+ typeId,+ ) import Covenant.Internal.Type ( AbstractTy (BoundAt), BuiltinFlatT@@ -150,9 +161,12 @@ runConstructorName, ) import Covenant.Internal.Unification (checkApp)+import Covenant.Prim (ThreeArgFunc (IfThenElse), TwoArgFunc (EqualsInteger)) import Covenant.Type- ( CompT (Comp0, CompN),- CompTBody (ArgsAndResult),+ ( CompT (Comp0, Comp1, Comp2, CompN),+ CompTBody (ArgsAndResult, ReturnT, (:--:>)),+ boolT,+ tyvar, ) import Covenant.Util (prettyStr) import Data.Coerce (coerce)@@ -167,6 +181,7 @@ import Data.Text qualified as T import Data.Vector (Vector) import Data.Vector qualified as Vector+import Data.Wedge (Wedge (Here)) import GHC.Exts (fromListN) import GHC.Word (Word32) import Optics.Core@@ -370,8 +385,13 @@ -- -- @since 1.1.0 tyAppTestDatatypes :: M.Map TyName (DatatypeInfo AbstractTy)-tyAppTestDatatypes =- foldl' (\acc decl -> M.insert (view #datatypeName decl) (unsafeMkDatatypeInfo decl) acc) M.empty testDatatypes+tyAppTestDatatypes = unsafeMkDatatypeInfos testDatatypes++-- | Construct a datatype information dictionary (which is what the ASGBuilder actually needs)+-- from a collection of data declarations (which are what people actually write)+-- @since 1.3.0+unsafeMkDatatypeInfos :: [DataDeclaration AbstractTy] -> M.Map TyName (DatatypeInfo AbstractTy)+unsafeMkDatatypeInfos = mappend primBaseFunctorInfos . foldl' (\acc decl -> unsafeMkDatatypeInfo decl <> acc) M.empty where unsafeMkDatatypeInfo d = case mkDatatypeInfo d of Left err -> error (show err)@@ -929,4 +949,220 @@ (PlutusData ConstrData) testDatatypes :: [DataDeclaration AbstractTy]-testDatatypes = [maybeT, eitherT, unitT, pair, list]+testDatatypes = [maybeT, eitherT, unitT, pair, list, conformance_OpaqueFoo]++{- For conformance testing. All of the ledger types are data encoded, so we need some variants which+ are not data encoded to ensure adequate test coverage.+data Maybe a = Nothing | Just a++data Result e a = Exception e | OK a++data Pair a b = Pair a b++data List a = Nil | Cons a (List a)+-}+conformanceDatatypes1 :: [DataDeclaration AbstractTy]+conformanceDatatypes1 = [conformance_Maybe_SOP, conformance_Result, pair, list]++conformanceDatatypes2 :: [DataDeclaration AbstractTy]+conformanceDatatypes2 = [conformance_Void, conformance_OpaqueFoo, conformance_Maybe_SOP, pair]++conformance_Void :: DataDeclaration AbstractTy+conformance_Void = mkDecl $ Decl "Void" count0 [] SOP++conformance_OpaqueFoo :: DataDeclaration AbstractTy+conformance_OpaqueFoo = OpaqueData "Foo" (Set.fromList [PlutusI, PlutusB])++conformance_Maybe_SOP :: DataDeclaration AbstractTy+conformance_Maybe_SOP =+ mkDecl $+ Decl+ "Maybe"+ count1+ [ Ctor "Nothing" [],+ Ctor "Just" [tyvar Z ix0]+ ]+ SOP++conformance_Result :: DataDeclaration AbstractTy+conformance_Result =+ mkDecl $+ Decl+ "Result"+ count2+ [ Ctor "Exception" [tyvar Z ix0],+ Ctor "OK" [tyvar Z ix1]+ ]+ (PlutusData ConstrData)++-- These are canned tests for concretification. They do double duty as tests for+-- part of the code generator, so we put them here to avoid having to duplicate them across+-- repositories.++intT :: ValT AbstractTy+intT = BuiltinFlat IntegerT++mkMaybeT :: ValT AbstractTy -> ValT AbstractTy+mkMaybeT t = dtype "Maybe" [t]++ix4 :: forall s. Index s+ix4 = fromJust $ preview intIndex 4++thunk0 :: forall (a :: Type). CompTBody a -> ValT a+thunk0 = ThunkT . Comp0++(#==) :: Ref -> Ref -> ASGBuilder Ref+x #== y = do+ equals <- builtin2 EqualsInteger+ AnId <$> app' equals [x, y]++-- This is the minimal example, largely for testing that our fix to the concretification/unification glitch+-- in this repo works.+concretifyMinimalBuilder :: ASGBuilder Id+concretifyMinimalBuilder = lam topLevelTy body+ where+ topLevelTy :: CompT AbstractTy+ topLevelTy = Comp0 $ intT :--:> boolT :--:> ReturnT intT++ body :: ASGBuilder Ref+ body = do+ intArg <- AnArg <$> arg Z ix0+ boolArg <- AnArg <$> arg Z ix1+ fElim <- fPolyOneElimMinimal+ maybeInt <- ctor' "Maybe" "Just" [intArg]+ AnId <$> app' fElim [AnId maybeInt, boolArg]++ fPolyOneElimMinimal :: ASGBuilder Id+ fPolyOneElimMinimal = lam fPolyOneElimTy $ do+ maybeA <- AnArg <$> arg Z ix0+ nothingHandler <- lazyLam (Comp0 $ ReturnT intT) $ do+ AnId <$> lit (AnInteger 0)+ justHandler <- lazyLam (Comp0 $ tyvar (S Z) ix0 :--:> ReturnT intT) $ do+ AnId <$> lit (AnInteger 0)+ AnId <$> match maybeA [AnId justHandler, AnId nothingHandler]+ where+ fPolyOneElimTy :: CompT AbstractTy+ fPolyOneElimTy =+ Comp2 $ -- forall a b.+ mkMaybeT (tyvar Z ix0) -- Maybe a+ :--:> tyvar Z ix1 -- b+ :--:> ReturnT intT -- Int++-- This is a torture test example. We'll use it here since we have it and it's relevant, but it's largely+-- designed for a test in the code generator.+concretifyMegaTest :: ASGBuilder Id+concretifyMegaTest = lam topLevelTy body+ where+ body :: ASGBuilder Ref+ body = do+ intArg <- AnArg <$> arg Z ix0+ boolArg <- AnArg <$> arg Z ix1+ idF <- AnId <$> (thunk =<< identitee)+ fmono <- AnId <$> (thunk =<< fMono)+ gmono <- AnId <$> (thunk =<< gMono)+ mConst <- AnId <$> (thunk =<< monoConst)+ fIntro <- fPolyOneIntro+ fElim <- fPolyOneElim+ introApplied <- app' fIntro [fmono, mConst, gmono, intArg, boolArg]+ -- REVIEW @Koz: You said that a fully polymorphic identity function *should not work* as the second arg here, but it does work?+ AnId <$> app' fElim [AnId introApplied, idF, boolArg, fmono]++ ifte :: ASGBuilder Id+ ifte = lam (Comp1 $ boolT :--:> tyvar Z ix0 :--:> tyvar Z ix0 :--:> ReturnT (tyvar Z ix0)) $ do+ cond <- AnArg <$> arg Z ix0+ t <- AnArg <$> arg Z ix1+ f <- AnArg <$> arg Z ix2+ ifThen <- builtin3 IfThenElse+ AnId <$> app' ifThen [cond, t, f]++ -- NOTE: I wonder what happens if we tried to define id and monoConst in terms of each other?+ -- Like if we do it so that we get infinite mutual recursion. I should probably *try*+ -- to compile something like that just to see whether it explodes.+ identitee :: ASGBuilder Id+ identitee = lam (Comp1 $ tyvar Z ix0 :--:> ReturnT (tyvar Z ix0)) $ do+ -- Not how you would typically implement `id` lol+ mConst <- monoConst+ x <- AnArg <$> arg Z ix0+ -- Might not need the ty app?+ AnId <$> app' mConst [x, x] -- [Here tyX]++ -- forall a. a -> a -> a+ monoConst :: ASGBuilder Id+ monoConst = lam (Comp1 $ tyvar Z ix0 :--:> tyvar Z ix0 :--:> ReturnT (tyvar Z ix0)) $ do+ AnArg <$> arg Z ix1++ topLevelTy :: CompT AbstractTy+ topLevelTy = Comp0 $ intT :--:> boolT :--:> ReturnT intT++ fPolyOneElim :: ASGBuilder Id+ fPolyOneElim = lam fPolyOneElimTy $ do+ zero <- AnId <$> lit (AnInteger 0)+ maybeA <- AnArg <$> arg Z ix0+ nothingHandler <- lazyLam (Comp0 $ ReturnT intT) $ do+ mConst <- monoConst+ b <- AnArg <$> arg (S Z) ix2+ bToInt <- force . AnArg =<< arg (S Z) ix3+ x <- AnId <$> app' bToInt [b]+ AnId <$> app' mConst [zero, x]+ justHandler <- lazyLam (Comp0 $ tyvar (S Z) ix0 :--:> ReturnT intT) $ do+ aToInt <- force . AnArg =<< arg (S Z) ix1+ a <- AnArg <$> arg Z ix0+ AnId <$> app' aToInt [a]+ AnId <$> match maybeA [AnId justHandler, AnId nothingHandler] -- ,AnId justHandler]+ where+ fPolyOneElimTy :: CompT AbstractTy+ fPolyOneElimTy =+ Comp2 $ -- forall a b.+ mkMaybeT (tyvar Z ix0) -- Maybe a+ :--:> thunk0 (tyvar (S Z) ix0 :--:> ReturnT intT) -- (a -> Int)+ :--:> tyvar Z ix1 -- b+ :--:> thunk0 (tyvar (S Z) ix1 :--:> ReturnT intT) -- (b -> Int)+ :--:> ReturnT intT -- Int+ fPolyOneIntro :: ASGBuilder Id+ fPolyOneIntro = lam fPolyOneIntroTy $ do+ fba <- force . AnArg =<< arg Z ix0+ faa <- force . AnArg =<< arg Z ix1+ predA <- force . AnArg =<< arg Z ix2+ a <- AnArg <$> arg Z ix3+ b <- AnArg <$> arg Z ix4+ -- let ba = fba (monoConst b b)+ mConst <- monoConst+ fbaArg <- AnId <$> app' mConst [b, b]+ ba <- AnId <$> app' fba [fbaArg]+ -- let aaa = monoConst a (faa a ba)+ aaaArg <- AnId <$> app' faa [a, ba]+ aaa <- AnId <$> app' mConst [a, aaaArg]+ -- if (predA aaa) then Nothing else Just aaa+ tvA <- boundTyVar Z ix0+ nothing <- ctor "Maybe" "Nothing" [] [Here tvA]+ justAAA <- ctor' "Maybe" "Just" [aaa]+ ifThen <- ifte+ cond <- app' predA [aaa]+ AnId <$> app' ifThen [AnId cond, AnId nothing, AnId justAAA]+ where+ fPolyOneIntroTy :: CompT AbstractTy+ fPolyOneIntroTy =+ Comp2 $ -- forall a b.+ ThunkT (Comp0 $ tyvar (S Z) ix1 :--:> ReturnT (tyvar (S Z) ix0)) -- (b -> a)+ :--:> ThunkT (Comp0 $ tyvar (S Z) ix0 :--:> tyvar (S Z) ix0 :--:> ReturnT (tyvar (S Z) ix0)) -- (a -> a -> a)+ :--:> ThunkT (Comp0 $ tyvar (S Z) ix0 :--:> ReturnT boolT) -- (a -> Bool)+ :--:> tyvar Z ix0 -- a+ :--:> tyvar Z ix1 -- b+ :--:> ReturnT (mkMaybeT (tyvar Z ix0)) -- Maybe a+ fMono :: ASGBuilder Id+ fMono = lam (Comp0 $ boolT :--:> ReturnT intT) $ do+ aBool <- AnArg <$> arg Z ix0+ one <- AnId <$> lit (AnInteger 1)+ zero <- AnId <$> lit (AnInteger 0)+ ifThen <- ifte+ AnId <$> app' ifThen [aBool, one, zero]++ gMono :: ASGBuilder Id+ gMono = lam (Comp0 $ intT :--:> ReturnT boolT) $ do+ anInt <- AnArg <$> arg Z ix0+ zero <- AnId <$> lit (AnInteger 0)+ cond <- anInt #== zero+ ifThen <- ifte+ false <- AnId <$> lit (ABoolean False)+ troo <- AnId <$> lit (ABoolean True)+ AnId <$> app' ifThen [cond, false, troo]
src/Covenant/Type.hs view
@@ -235,9 +235,10 @@ -- -- @since 1.0.0 pattern CompN ::+ forall (a :: Type). Count "tyvar" ->- CompTBody AbstractTy ->- CompT AbstractTy+ CompTBody a ->+ CompT a pattern CompN count xs <- CompT count xs where CompN count xs = CompT count xs
+ src/Covenant/Zipper.hs view
@@ -0,0 +1,281 @@+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE ViewPatterns #-}++-- |+-- Module: Covenant.Zipper+-- Copyright: (C) MLabs 2025+-- License: Apache 2.0+-- Maintainer: koz@mlabs.city, sean@mlabs.city+--+-- A read-only zipper for the Covenant ASG, based on an action monad.+--+-- @since 1.3.0+module Covenant.Zipper+ ( -- * Types+ ZipperAction,+ Tape (..),+ ZipperState (WorkingZipper, BrokenZipper),+ ASGZipper,++ -- * Functions++ -- ** Actions+ moveUp,+ moveDown,+ moveLeft,+ moveRight,+ resetZipper,++ -- ** Elimination+ runASGZipper,+ )+where++import Control.Monad.Action+ ( Action (StateOf, act),+ Actionable,+ MonadUpdate,+ UpdateT,+ actionable,+ runUpdateT,+ )+import Covenant.ASG+ ( ASG,+ ASGNode (ACompNode, AValNode, AnError),+ Arg,+ CompNodeInfo (Force, Lam),+ Id,+ Ref (AnArg, AnId),+ ValNodeInfo (App, Cata, DataConstructor, Match, Thunk),+ nodeAt,+ topLevelId,+ )+import Covenant.Util (pattern ConsV, pattern NilV)+import Data.Functor.Identity (Identity, runIdentity)+import Data.Kind (Type)+import Data.Monoid (Endo (Endo))+import Data.Vector qualified as Vector+import GHC.Exts (toList)++-- | A requested movement from the zipper. To build these, use dedicated smart+-- constructors in this module. You can \'chain together\' 'ZipperAction' using+-- the 'Semigroup' instance.+--+-- @since 1.3.0+newtype ZipperAction = ZipperAction (Actionable ZipperStep)+ deriving+ ( -- | @since 1.3.0+ Semigroup,+ -- | @since 1.3.0+ Monoid+ )+ via (Actionable ZipperStep)++-- | @since 1.3.0+instance Action ZipperAction where+ type StateOf ZipperAction = ZipperState+ act (ZipperAction acts) = foldMap go acts+ where+ go :: ZipperStep -> Endo ZipperState+ go =+ Endo . \case+ ZipperDown -> downStep+ ZipperUp -> upStep+ ZipperLeft -> leftStep+ ZipperRight -> rightStep+ ZipperReset -> resetStep++-- | Move towards the source of the ASG, \'back up\' along the path taken to+-- reach the current position. Will put the zipper in a broken state if used at+-- the source node.+--+-- @since 1.3.0+moveUp :: ZipperAction+moveUp = ZipperAction . actionable $ ZipperUp++-- | Move to the leftmost child of the current position. Will put the zipper in+-- a broken state if used at a sink node.+--+-- @since 1.3.0+moveDown :: ZipperAction+moveDown = ZipperAction . actionable $ ZipperDown++-- | Move to the sibling immediately to the left of the current position. Will+-- put the zipper in a broken state if used at a leftmost sibling.+--+-- @since 1.3.0+moveLeft :: ZipperAction+moveLeft = ZipperAction . actionable $ ZipperLeft++-- | Move to the sibling immediately to the right of the current position. Will+-- put the zipper in a broken state if used at a rightmost sibling.+--+-- @since 1.3.0+moveRight :: ZipperAction+moveRight = ZipperAction . actionable $ ZipperRight++-- | If the zipper is currently in a broken state, reset it to the last position+-- it was at before breaking. Otherwise, this does nothing.+--+-- @since 1.3.0+resetZipper :: ZipperAction+resetZipper = ZipperAction . actionable $ ZipperReset++-- | A \'list with a focus\', which may be of a different type to the rest. The+-- first field is \'backwards\', in that its first element is actually the+-- /furthest/ from the focus. Thus, if we have @Tape [3, 2, 1] "foo" [4, 5]@,+-- the \'list\' actually looks like this:+--+-- @[1, 2, 3, "foo", 4, 5]@+--+-- but /not/ like this:+--+-- @[3, 2, 1, "foo", 4, 5]@+--+-- @since 1.3.0+data Tape a b = Tape [a] b [a]+ deriving stock+ ( -- | @since 1.3.0+ Functor+ )++-- | The current state of the zipper, including whether it's in a broken state+-- or not, and if not in a broken state, the current position and the path taken+-- to get here.+--+-- @since 1.3.0+data ZipperState = ZipperState Bool ASG [Tape Ref Id] (Tape Ref Ref)++-- | Matches on a working zipper, giving access to a stack of 'Tape's+-- representing the path taken to get here (tracking sibling positions) and the+-- current position, with the focus at either an 'Arg' or an 'ASGNode'.+--+-- Parent positions use 'Id' for the focus, as 'Arg's cannot have descendants.+--+-- @since 1.3.0+pattern WorkingZipper :: [Tape Ref Id] -> Tape Ref (Either Arg (Id, ASGNode)) -> ZipperState+pattern WorkingZipper parents curr <- ZipperState False g parents (getNodeInfo g -> curr)++-- | Matches on a zipper in a broken state.+--+-- @since 1.3.0+pattern BrokenZipper :: ZipperState+pattern BrokenZipper <- ZipperState True _ _ _++{-# COMPLETE WorkingZipper, BrokenZipper #-}++-- | A \'zipper command monad\', designed to traverse an ASG. Based on an action+-- monad.+--+-- To perform zipper moves, use 'Control.Monad.Action.send' together with a+-- 'ZipperAction'. If you want to find out something about where we're standing,+-- use 'Control.Monad.Action.request', together with pattern matching on+-- 'ZipperState'.+--+-- @since 1.3.0+newtype ASGZipper (a :: Type)+ = ASGZipper (UpdateT ZipperAction Identity a)+ deriving+ ( -- | @since 1.3.0+ Functor,+ -- | @since 1.3.0+ Applicative,+ -- | @since 1.3.0+ Monad,+ -- | @since 1.3.0+ MonadUpdate ZipperAction+ )+ via (UpdateT ZipperAction Identity)++-- | Perform the stated actions to traverse over the 'ASG' given by the+-- argument.+--+-- @since 1.3.0+runASGZipper ::+ forall (a :: Type).+ ASG ->+ ASGZipper a ->+ a+runASGZipper g (ASGZipper comp) =+ let i = topLevelId g+ in (\(_, _, x) -> x)+ . runIdentity+ . runUpdateT comp+ . ZipperState False g []+ . Tape [] (AnId i)+ $ []++-- Helpers++data ZipperStep = ZipperDown | ZipperUp | ZipperLeft | ZipperRight | ZipperReset+ deriving stock (Eq, Show)++downStep :: ZipperState -> ZipperState+downStep zs@(ZipperState walkedOff g parentLevels currentLevel) =+ if walkedOff+ then zs+ else case currentLevel of+ Tape lefts curr rights ->+ let miss = ZipperState True g parentLevels currentLevel+ in case curr of+ AnArg _ -> miss+ AnId i ->+ let next = ZipperState walkedOff g (Tape lefts i rights : parentLevels)+ in case nodeAt i g of+ ACompNode _ info -> case info of+ Lam r -> next . Tape [] r $ []+ Force r -> next . Tape [] r $ []+ _ -> miss+ AValNode _ info -> case info of+ App f args _ _ -> next . Tape [] (AnId f) . toList $ args+ Thunk f -> next . Tape [] (AnId f) $ []+ Cata _ arms x -> case arms of+ NilV -> zs -- impossible+ ConsV c cs -> next . Tape [] c . toList . Vector.snoc cs $ x -- next . Tape [] alg $ [x]+ DataConstructor _ _ args -> case args of+ NilV -> miss+ ConsV arg args' -> next . Tape [] arg . toList $ args'+ Match x handlers -> next . Tape [] x . toList $ handlers+ _ -> miss+ AnError -> miss++upStep :: ZipperState -> ZipperState+upStep zs@(ZipperState walkedOff g parentLevels currentLevel) =+ if walkedOff+ then zs+ else case parentLevels of+ [] -> ZipperState True g parentLevels currentLevel+ (p : ps) -> case p of+ Tape lefts curr rights -> ZipperState walkedOff g ps . Tape lefts (AnId curr) $ rights++leftStep :: ZipperState -> ZipperState+leftStep zs@(ZipperState walkedOff g parentLevels currentLevel) =+ if walkedOff+ then zs+ else case currentLevel of+ Tape lefts curr rights -> case lefts of+ [] -> ZipperState True g parentLevels currentLevel+ (l : ls) -> ZipperState walkedOff g parentLevels . Tape ls l $ curr : rights++rightStep :: ZipperState -> ZipperState+rightStep zs@(ZipperState walkedOff g parentLevels currentLevel) =+ if walkedOff+ then zs+ else case currentLevel of+ Tape lefts curr rights -> case rights of+ [] -> ZipperState True g parentLevels currentLevel+ (r : rs) -> ZipperState walkedOff g parentLevels . Tape (curr : lefts) r $ rs++resetStep :: ZipperState -> ZipperState+resetStep zs@(ZipperState walkedOff g parentLevels currentLevel) =+ if walkedOff+ then ZipperState False g parentLevels currentLevel+ else zs++getNodeInfo :: ASG -> Tape Ref Ref -> Tape Ref (Either Arg (Id, ASGNode))+getNodeInfo g =+ fmap+ ( \case+ AnId i -> Right (i, nodeAt i g)+ AnArg arg -> Left arg+ )
test/asg/Main.hs view
@@ -24,7 +24,7 @@ ( ApplyCompType, ApplyToError, ApplyToValType,- CataNoBaseFunctorForType,+ BaseFunctorDoesNotExistFor, CataNonRigidAlgebra, ForceCompType, ForceError,@@ -38,13 +38,16 @@ Ref (AnArg, AnId), ValNodeInfo (Lit), app,+ app', arg,+ baseFunctorOf, boundTyVar, builtin1, builtin2, builtin3, cata, ctor,+ ctor', dataConstructor, defaultDatatypes, dtype,@@ -54,6 +57,8 @@ lazyLam, lit, match,+ naturalBF,+ negativeBF, runASGBuilder, thunk, topLevelNode,@@ -63,7 +68,7 @@ typeConstant, ) import Covenant.DeBruijn (DeBruijn (S, Z))-import Covenant.Index (Index, intIndex, ix0, ix1)+import Covenant.Index (Index, intIndex, ix0, ix1, ix2) import Covenant.Prim ( typeOneArgFunc, typeThreeArgFunc,@@ -72,13 +77,15 @@ import Covenant.Test ( Concrete (Concrete), DebugASGBuilder,+ concretifyMegaTest,+ concretifyMinimalBuilder, debugASGBuilder, tyAppTestDatatypes, typeIdTest, ) import Covenant.Type ( AbstractTy,- BuiltinFlatT (IntegerT, UnitT),+ BuiltinFlatT (ByteStringT, IntegerT, UnitT), CompT (Comp0, Comp1, Comp2, CompN), CompTBody (ArgsAndResult, ReturnT, (:--:>)), ValT (BuiltinFlat, Datatype, ThunkT),@@ -94,7 +101,7 @@ import Data.Map qualified as M import Data.Maybe (fromJust) import Data.Vector qualified as Vector-import Data.Wedge (Wedge (Here, Nowhere, There), wedgeLeft)+import Data.Wedge (Wedge (Here, There), wedgeLeft) import Optics.Core (preview, review) import Test.QuickCheck ( Gen,@@ -143,15 +150,15 @@ justNothingIntro, testGroup "Catamorphisms"- [ testCase "Natural_F can tear down an Integer" unitCataNaturalF,- testCase "Negative_F can tear down an Integer" unitCataNegativeF,- testCase "ByteString_F can tear down a ByteString" unitCataByteStringF,+ [ testCase "#Natural can tear down an Integer" unitCataNaturalF,+ testCase "#Negative can tear down an Integer" unitCataNegativeF,+ testCase "#ByteString can tear down a ByteString" unitCataByteStringF, testCase "Non-recursive type cata should fail" unitCataMaybeF, testCase "Cata with non-rigid algebra should fail" unitCataNonRigidF,- testCase "<List_F Integer Bool -> !Bool> with List Integer should be Bool" unitCataListInteger,- testCase "<List_F Integer r -> !r> with List Integer should be r" unitCataListIntegerRigid,- testCase "<List_F r Integer -> !Integer> with List r should be Integer" unitCataListRigid,- testCase "<List_F r (Maybe r) -> !Maybe r> with List r should be Maybe r" unitCataListMaybeRigid,+ testCase "<#List Integer Bool -> !Bool> with List Integer should be Bool" unitCataListInteger,+ testCase "<#List Integer r -> !r> with List Integer should be r" unitCataListIntegerRigid,+ testCase "<#List r Integer -> !Integer> with List r should be Integer" unitCataListRigid,+ testCase "<#List r (Maybe r) -> !Maybe r> with List r should be Maybe r" unitCataListMaybeRigid, testCase "introduction then cata elimination" unitCataIntroThenEliminate ], testGroup@@ -159,7 +166,13 @@ [ matchMaybe, matchList, maybeToList- ]+ ],+ testGroup+ "Opaque"+ [unifyOpaque],+ testGroup "matchOpaque" [matchOpaque],+ testGroup "argBugTest" [argBugUnitTest],+ testGroup "concretify" [simpleConcretifyUnitTest, excessiveConcretifyUnitTest] ] where moreTests :: QuickCheckTests -> QuickCheckTests@@ -197,176 +210,250 @@ Left (TypeError _ ThunkError) -> pure () _ -> assertFailure $ "Unexpected result: " <> show result --- Construct a function of type `<Natural_F Bool -> !Bool> -> Integer -> !Bool`, whose--- body performs a cata over its second argument using its first argument. This--- should compile, and type as expected.+-- Construct a function of type `Bool -> <Bool -> !Bool> -> Integer ->+-- !Bool`, whose body performs a cata over its third argument using the first+-- and second argument as handlers. The stated algebra type is `Natural Bool ->+-- !Bool`, where `Natural` refers to the natural-number base functor of+-- `Integer`. This should compile and type as expected. unitCataNaturalF :: IO () unitCataNaturalF = do- let thunkTy = ThunkT $ Comp0 $ Datatype "Natural_F" [boolT] :--:> ReturnT boolT- let ty = Comp0 $ thunkTy :--:> integerT :--:> ReturnT boolT- let comp = lam ty $ do- alg <- arg Z ix0- x <- arg Z ix1- result <- cata (AnArg alg) (AnArg x)- pure . AnId $ result- withCompilationSuccessUnit comp $ matchesType ty+ let algTy = Comp0 $ Datatype naturalBF [boolT] :--:> ReturnT boolT+ let resultTy =+ Comp0 $+ boolT+ :--:> ThunkT (Comp0 $ boolT :--:> ReturnT boolT)+ :--:> integerT+ :--:> ReturnT boolT+ let comp = lam resultTy $ do+ armZ <- arg Z ix0+ armS <- arg Z ix1+ x <- arg Z ix2+ AnId <$> cata algTy (Vector.fromList . fmap AnArg $ [armZ, armS]) (AnArg x)+ withCompilationSuccessUnit comp (matchesType resultTy) --- Construct a function of type `<Negative_F Bool -> !Bool> -> Integer -> !Bool`, whose--- body performs a cata over its second argument using its first argument. This--- should compile, and type as expected.+-- Construct a function of type `Bool -> <Bool -> !Bool> -> Integer ->+-- !Bool`, whose body performs a cata over its third argument using the first+-- and second argument as handlers. The stated algebra type is `Negative Bool -> !Bool`,+-- where `Negative` refers to the negative-number base functor of `Integer`.+-- This should compile and type as expected. unitCataNegativeF :: IO () unitCataNegativeF = do- let thunkTy = ThunkT $ Comp0 $ Datatype "Negative_F" [boolT] :--:> ReturnT boolT- let ty = Comp0 $ thunkTy :--:> integerT :--:> ReturnT boolT- let comp = lam ty $ do- alg <- arg Z ix0- x <- arg Z ix1- result <- cata (AnArg alg) (AnArg x)- pure . AnId $ result- withCompilationSuccessUnit comp $ matchesType ty+ let algTy = Comp0 $ Datatype negativeBF [boolT] :--:> ReturnT boolT+ let resultTy =+ Comp0 $+ boolT+ :--:> ThunkT (Comp0 $ boolT :--:> ReturnT boolT)+ :--:> integerT+ :--:> ReturnT boolT+ let comp = lam resultTy $ do+ armZ <- arg Z ix0+ armS <- arg Z ix1+ x <- arg Z ix2+ AnId <$> cata algTy (Vector.fromList . fmap AnArg $ [armZ, armS]) (AnArg x)+ withCompilationSuccessUnit comp (matchesType resultTy) --- Construct a function of type `<ByteString_F Integer -> !Integer> -> ByteString--- -> !Bool`, whose body performs a cata over its second argument using its--- first argument. This should compile, and type as expected.+-- Construct a function of type `Bool -> <Integer -> Bool -> !Bool>+-- -> ByteString -> !Bool>, whose body performs a cata over its third argument+-- using the first and second argument as handlers. The stated algebra type is+-- `ByteString# Bool -> !Bool`, where `ByteString#` refers to the base functor+-- of `ByteString`. This should compile and type as expected. unitCataByteStringF :: IO () unitCataByteStringF = do- let thunkTy = ThunkT $ Comp0 $ Datatype "ByteString_F" [integerT] :--:> ReturnT integerT- let ty = Comp0 $ thunkTy :--:> byteStringT :--:> ReturnT integerT- let comp = lam ty $ do- alg <- arg Z ix0- x <- arg Z ix1- result <- cata (AnArg alg) (AnArg x)- pure . AnId $ result- withCompilationSuccessUnit comp $ matchesType ty+ let algTy = do+ bfName <- baseFunctorOf "ByteString"+ pure . Comp0 $ Datatype bfName [boolT] :--:> ReturnT boolT+ let resultTy =+ Comp0 $+ boolT+ :--:> ThunkT (Comp0 $ integerT :--:> boolT :--:> ReturnT boolT)+ :--:> byteStringT+ :--:> ReturnT boolT+ let comp = lam resultTy $ do+ t <- algTy+ armNil <- arg Z ix0+ armCons <- arg Z ix1+ x <- arg Z ix2+ AnId <$> cata t (Vector.fromList . fmap AnArg $ [armNil, armCons]) (AnArg x)+ withCompilationSuccessUnit comp (matchesType resultTy) --- Construct a function of type `forall a . <Maybe_F a Integer -> !Integer> -> Maybe--- a -> !Integer`, whose body performs a cata over its second argument--- using its first argument. This should fail to compile, indicating that--- `Maybe` doesn't have a base functor.+-- Construct a function of type `forall a . Integer -> <a -> !Integer> ->+-- Maybe a -> !Integer`, whose body performs a cata over its third argument+-- using the first and second argument as handlers. The stated algebra type is+-- `Maybe# a Integer -> !Integer`, with `a` rigid. This should fail to compile,+-- indicating that `Maybe` lacks a base functor. unitCataMaybeF :: IO () unitCataMaybeF = do- let thunkTy = ThunkT $ Comp0 $ Datatype "Maybe_F" [tyvar (S Z) ix0, integerT] :--:> ReturnT integerT- let ty = Comp1 $ thunkTy :--:> Datatype "Maybe" [tyvar Z ix0] :--:> ReturnT integerT- let comp = lam ty $ do- alg <- arg Z ix0- x <- arg Z ix1- result <- cata (AnArg alg) (AnArg x)- pure . AnId $ result+ let algTy = do+ bfName <- baseFunctorOf "Maybe"+ pure . Comp0 $ Datatype bfName [tyvar (S Z) ix0, integerT] :--:> ReturnT integerT+ let resultTy =+ Comp1 $+ integerT+ :--:> ThunkT (Comp0 $ tyvar (S Z) ix0 :--:> ReturnT integerT)+ :--:> Datatype "Maybe" [tyvar Z ix0]+ :--:> ReturnT integerT+ let comp = lam resultTy $ do+ t <- algTy+ armNothing <- arg Z ix0+ armJust <- arg Z ix1+ x <- arg Z ix2+ AnId <$> cata t (Vector.fromList . fmap AnArg $ [armNothing, armJust]) (AnArg x) withCompilationFailureUnit comp $ \case- TypeError _ (CataNoBaseFunctorForType tyName) -> assertEqual "" "Maybe" tyName+ TypeError _ (BaseFunctorDoesNotExistFor tyName) -> assertEqual "" "Maybe" tyName err' -> assertFailure $ "Failed with unexpected type of error: " <> show err' --- Construct a function of type `<forall a . ListF a (Maybe a) -> !Maybe a> -> List--- Integer -> !Maybe Integer`, whose body performs a cata over its second--- argument using its first argument. This should fail to compile due to a--- non-rigid algebra.+-- Construct a function of type forall a . Maybe a -> <a -> Maybe a ->+-- !(Maybe a)> ->+-- List Integer -> !(Maybe Integer)`, whose body performs a cata over its third+-- argument using the first and second argument as handlers. The stated algebra+-- type is `forall b. List# b (Maybe b) -> !(Maybe b)`. This should fail to+-- compile due to a non-rigid algebra. unitCataNonRigidF :: IO () unitCataNonRigidF = do- let nonRigidCompT = Comp1 $ Datatype "List_F" [tyvar Z ix0, Datatype "Maybe" [tyvar Z ix0]] :--:> ReturnT (Datatype "Maybe" [tyvar Z ix0])- let thunkTy = ThunkT nonRigidCompT- let ty = Comp0 $ thunkTy :--:> Datatype "List" [integerT] :--:> ReturnT (Datatype "Maybe" [integerT])- let comp = lam ty $ do- alg <- arg Z ix0- x <- arg Z ix1- result <- cata (AnArg alg) (AnArg x)- pure . AnId $ result+ let algTy = do+ listBfName <- baseFunctorOf "List"+ pure . Comp1 $ Datatype listBfName [tyvar Z ix0, Datatype "Maybe" [tyvar Z ix0]] :--:> ReturnT (Datatype "Maybe" [tyvar Z ix0])+ let resultTy =+ Comp1 $+ Datatype "Maybe" [tyvar (S Z) ix0]+ :--:> ThunkT (Comp0 $ tyvar (S Z) ix0 :--:> Datatype "Maybe" [tyvar (S Z) ix0] :--:> ReturnT (Datatype "Maybe" [tyvar (S Z) ix0]))+ :--:> Datatype "List" [tyvar Z ix0]+ :--:> ReturnT (Datatype "Maybe" [tyvar Z ix0])+ let comp = lam resultTy $ do+ t <- algTy+ armNil <- arg Z ix0+ armCons <- arg Z ix1+ x <- arg Z ix2+ AnId <$> cata t (Vector.fromList . fmap AnArg $ [armNil, armCons]) (AnArg x) withCompilationFailureUnit comp $ \case- TypeError _ (CataNonRigidAlgebra t) -> assertEqual "" nonRigidCompT t+ TypeError _ (CataNonRigidAlgebra _) -> pure () err' -> assertFailure $ "Failed with unexpected type of error: " <> show err' --- Construct a function of type `<List_F Integer Bool -> !Bool> -> List Integer--- -> !Bool`, whose body performs a cata over its second argument using its--- first argument. This should compile, and type as expected.+-- Construct a function of type `Bool -> <Integer -> Bool -> !Bool> -> List+-- Integer -> !Bool`, whose body performs a cata over its third argument using+-- the first and second argument as handlers. The stated algebra type is `List#+-- Integer Bool -> !Bool`. This should compile and type as expected. unitCataListInteger :: IO () unitCataListInteger = do- let thunkTy = ThunkT $ Comp0 $ Datatype "List_F" [integerT, boolT] :--:> ReturnT boolT- let ty = Comp0 $ thunkTy :--:> Datatype "List" [integerT] :--:> ReturnT boolT- let comp = lam ty $ do- alg <- arg Z ix0- x <- arg Z ix1- result <- cata (AnArg alg) (AnArg x)- pure . AnId $ result- withCompilationSuccessUnit comp $ matchesType ty+ let algTy = do+ listBfName <- baseFunctorOf "List"+ pure . Comp0 $ Datatype listBfName [integerT, boolT] :--:> ReturnT boolT+ let resultTy =+ Comp0 $+ boolT+ :--:> ThunkT (Comp0 $ integerT :--:> boolT :--:> ReturnT boolT)+ :--:> Datatype "List" [integerT]+ :--:> ReturnT boolT+ let comp = lam resultTy $ do+ t <- algTy+ armNil <- arg Z ix0+ armCons <- arg Z ix1+ x <- arg Z ix2+ AnId <$> cata t (Vector.fromList . fmap AnArg $ [armNil, armCons]) (AnArg x)+ withCompilationSuccessUnit comp (matchesType resultTy) --- Construct a function of type `forall a . <List_F Integer a -> !a> -> List--- Integer -> !a`, whose body performs a cata over its second argument using its--- first argument. This should compile, and type as expected.+-- Construct a function of type `forall a . a -> <!Integer -> a -> !a> ->+-- List Integer -> !a>, whose body performs a cata over its third argument using+-- the first and second argument as handlers. The stated algebra type is `List#+-- Integer a -> !a`, with `a` rigid. This should compile and type as expected. unitCataListIntegerRigid :: IO () unitCataListIntegerRigid = do- let thunkTy = ThunkT $ Comp0 $ Datatype "List_F" [integerT, tyvar (S Z) ix0] :--:> ReturnT (tyvar (S Z) ix0)- let ty = Comp1 $ thunkTy :--:> Datatype "List" [integerT] :--:> ReturnT (tyvar Z ix0)- let comp = lam ty $ do- alg <- arg Z ix0- x <- arg Z ix1- result <- cata (AnArg alg) (AnArg x)- pure . AnId $ result- withCompilationSuccessUnit comp $ matchesType ty+ let algTy = do+ listBfName <- baseFunctorOf "List"+ pure . Comp0 $ Datatype listBfName [integerT, tyvar (S Z) ix0] :--:> ReturnT (tyvar (S Z) ix0)+ let resultTy =+ Comp1 $+ tyvar Z ix0+ :--:> ThunkT (Comp0 $ integerT :--:> tyvar (S Z) ix0 :--:> ReturnT (tyvar (S Z) ix0))+ :--:> Datatype "List" [integerT]+ :--:> ReturnT (tyvar Z ix0)+ let comp = lam resultTy $ do+ t <- algTy+ armNil <- arg Z ix0+ armCons <- arg Z ix1+ x <- arg Z ix2+ AnId <$> cata t (Vector.fromList . fmap AnArg $ [armNil, armCons]) (AnArg x)+ withCompilationSuccessUnit comp (matchesType resultTy) --- Construct a function of type `forall a . <List_F a Integer -> !Integer> -> List--- a -> !Integer`, whose body performs a cata over its second argument using its--- first argument. This should compile, and type as expected.+-- Construct a function of type `forall a . Integer -> <a -> Integer ->+-- !Integer> -> List a -> !Integer`, whose body performs a cata over its third+-- argument using the first and second argument as handlers. The stated algebra+-- type is `List# a Integer -> !Integer`, holding `a` rigid. This should compile+-- and type as expected. unitCataListRigid :: IO () unitCataListRigid = do- let thunkTy = ThunkT $ Comp0 $ Datatype "List_F" [tyvar (S Z) ix0, integerT] :--:> ReturnT integerT- let ty = Comp1 $ thunkTy :--:> Datatype "List" [tyvar Z ix0] :--:> ReturnT integerT- let comp = lam ty $ do- alg <- arg Z ix0- x <- arg Z ix1- result <- cata (AnArg alg) (AnArg x)- pure . AnId $ result- withCompilationSuccessUnit comp $ matchesType ty+ let algTy = do+ listBfName <- baseFunctorOf "List"+ pure . Comp0 $ Datatype listBfName [tyvar (S Z) ix0, integerT] :--:> ReturnT integerT+ let resultTy =+ Comp1 $+ integerT+ :--:> ThunkT (Comp0 $ tyvar (S Z) ix0 :--:> integerT :--:> ReturnT integerT)+ :--:> Datatype "List" [tyvar Z ix0]+ :--:> ReturnT integerT+ let comp = lam resultTy $ do+ t <- algTy+ armNil <- arg Z ix0+ armCons <- arg Z ix1+ x <- arg Z ix2+ AnId <$> cata t (Vector.fromList . fmap AnArg $ [armNil, armCons]) (AnArg x)+ withCompilationSuccessUnit comp (matchesType resultTy) --- Construct a function of type `forall a . <List_F a (Maybe a) -> !Maybe a> ->--- List a -> !Maybe a`, whose body performs a cata over its second argument--- using its first argument. This should compile, and type as expected.+-- Construct a function of type `forall a . Maybe a -> <a -> Maybe a ->+-- !(Maybe a)> -> List a -> !(Maybe a)`, whose body performs a cata over its+-- third argument using the first and second argument as handlers. The stated+-- algebra type is `List# a (Maybe a) -> !(Maybe a)`, keeping `a` rigid. This+-- should compile and type as expected. unitCataListMaybeRigid :: IO () unitCataListMaybeRigid = do- let thunkTy =- ThunkT $- Comp0 $- Datatype "List_F" [tyvar (S Z) ix0, Datatype "Maybe" [tyvar (S Z) ix0]]- :--:> ReturnT (Datatype "Maybe" [tyvar (S Z) ix0])- let ty =+ let algTy = do+ listBfName <- baseFunctorOf "List"+ pure . Comp0 $ Datatype listBfName [tyvar (S Z) ix0, Datatype "Maybe" [tyvar (S Z) ix0]] :--:> ReturnT (Datatype "Maybe" [tyvar (S Z) ix0])+ let resultTy = Comp1 $- thunkTy+ Datatype "Maybe" [tyvar Z ix0]+ :--:> ThunkT (Comp0 $ tyvar (S Z) ix0 :--:> Datatype "Maybe" [tyvar (S Z) ix0] :--:> ReturnT (Datatype "Maybe" [tyvar (S Z) ix0])) :--:> Datatype "List" [tyvar Z ix0] :--:> ReturnT (Datatype "Maybe" [tyvar Z ix0])- let comp = lam ty $ do- alg <- arg Z ix0- x <- arg Z ix1- result <- cata (AnArg alg) (AnArg x)- pure . AnId $ result- withCompilationSuccessUnit comp $ matchesType ty+ let comp = lam resultTy $ do+ t <- algTy+ armNil <- arg Z ix0+ armCons <- arg Z ix1+ x <- arg Z ix2+ AnId <$> cata t (Vector.fromList . fmap AnArg $ [armNil, armCons]) (AnArg x)+ withCompilationSuccessUnit comp (matchesType resultTy) --- Construct a function of type `forall a b . <List_F a (Maybe b) -> !Maybe b> ->--- a -> !Maybe b`. In its body, we construct a singleton list, then eliminate it--- using a cata with the first argument as the algebra. THis should compile and--- type as expected.+-- Construct a function of type `forall a b . Maybe b -> <a -> Maybe b ->+-- !(Maybe b)> -> a -> !(Maybe b)`. In its body, we construct a singleton list+-- using the third argument, then eliminate it using a cata with the first and+-- second argument as handlers. The stated type of the algebra is `List# a+-- (Maybe b) -> !(Maybe b)`, keeping `a, b` rigid. This should compile and type+-- as expected. unitCataIntroThenEliminate :: IO () unitCataIntroThenEliminate = do- let thunkTy =- ThunkT $- Comp0 $- Datatype "List_F" [tyvar (S Z) ix0, Datatype "Maybe" [tyvar (S Z) ix1]]- :--:> ReturnT (Datatype "Maybe" [tyvar (S Z) ix1])- let ty =+ let algTy = do+ listBfName <- baseFunctorOf "List"+ pure . Comp0 $ Datatype listBfName [tyvar (S Z) ix0, Datatype "Maybe" [tyvar (S Z) ix1]] :--:> ReturnT (Datatype "Maybe" [tyvar (S Z) ix1])+ let resultTy = Comp2 $- thunkTy+ Datatype "Maybe" [tyvar Z ix1]+ :--:> ThunkT (Comp0 $ tyvar (S Z) ix0 :--:> Datatype "Maybe" [tyvar (S Z) ix1] :--:> ReturnT (Datatype "Maybe" [tyvar (S Z) ix1])) :--:> tyvar Z ix0 :--:> ReturnT (Datatype "Maybe" [tyvar Z ix1])- let comp = lam ty $ do- alg <- arg Z ix0- x <- arg Z ix1+ let comp = lam resultTy $ do+ t <- algTy+ x <- arg Z ix2 nilThunk <- dataConstructor "List" "Nil" [] nilForced <- force (AnId nilThunk) aT <- boundTyVar Z ix0 nilApplied <- app nilForced [] [Here aT] singleThunk <- dataConstructor "List" "Cons" [AnArg x, AnId nilApplied] singleForced <- force (AnId singleThunk)- singleApplied <- app singleForced [] [Nowhere]- result <- cata (AnArg alg) (AnId singleApplied)- pure . AnId $ result- withCompilationSuccessUnit comp $ matchesType ty+ singleApplied <- app' singleForced []+ armNil <- arg Z ix0+ armCons <- arg Z ix1+ AnId <$> cata t (Vector.fromList . fmap AnArg $ [armNil, armCons]) (AnId singleApplied)+ withCompilationSuccessUnit comp (matchesType resultTy) -- Properties @@ -530,7 +617,7 @@ Left bi1 -> builtin1 bi1 Right (Left bi2) -> builtin2 bi2 Right (Right bi3) -> builtin3 bi3- app i (Vector.singleton . AnId $ arg') mempty+ app' i (Vector.singleton . AnId $ arg') in withCompilationFailure comp $ \case TypeError _ (ApplyCompType actualT) -> t === actualT TypeError _ err' -> failWrongTypeError err'@@ -700,14 +787,10 @@ justNothingIntro :: TestTree justNothingIntro = runIntroFormTest "justNothingIntro" expectedThunk $ do thunkL <- lam expectedComp $ do- nothingThunk <- dataConstructor "Maybe" "Nothing" mempty var <- boundTyVar Z ix0- nothingForced <- force (AnId nothingThunk)- nothingApplied <- app nothingForced mempty (Vector.singleton . wedgeLeft . Just $ var)- justNothing <- dataConstructor "Maybe" "Just" (Vector.singleton (AnId nothingApplied))- justNothingForced <- force (AnId justNothing)- justNothingApplied <- app justNothingForced mempty (Vector.singleton Nowhere)- pure (AnId justNothingApplied)+ nothing <- ctor "Maybe" "Nothing" mempty (Vector.singleton . wedgeLeft . Just $ var)+ justNothing <- ctor' "Maybe" "Just" (Vector.singleton (AnId nothing))+ pure (AnId justNothing) typeIdTest thunkL where expectedComp :: CompT AbstractTy@@ -733,7 +816,7 @@ matchMaybe :: TestTree matchMaybe = runIntroFormTest "matchMaybe" (BuiltinFlat IntegerT) $ do unit <- AnId <$> lit AUnit- scrutinee <- ctor "Maybe" "Just" (Vector.singleton unit) (Vector.singleton Nowhere)+ scrutinee <- ctor' "Maybe" "Just" (Vector.singleton unit) nothingHandler <- lazyLam (Comp0 $ ReturnT (BuiltinFlat IntegerT)) (AnId <$> lit (AnInteger 0)) justHandler <- lazyLam (Comp0 $ BuiltinFlat UnitT :--:> ReturnT (BuiltinFlat IntegerT)) (AnId <$> lit (AnInteger 1)) result <- match (AnId scrutinee) (AnId <$> Vector.fromList [justHandler, nothingHandler])@@ -747,12 +830,12 @@ matchList = runIntroFormTest "matchList" (BuiltinFlat IntegerT) $ do unit <- AnId <$> lit AUnit nilUnit <- ctor "List" "Nil" mempty (Vector.singleton $ There (BuiltinFlat UnitT))- scrutinee <- ctor "List" "Cons" (Vector.fromList [unit, AnId nilUnit]) (Vector.singleton Nowhere)+ scrutinee <- ctor' "List" "Cons" (Vector.fromList [unit, AnId nilUnit]) let nilHandlerTy = Comp0 $ ReturnT (BuiltinFlat IntegerT) consHandlerTy = Comp0 $ BuiltinFlat UnitT- :--:> Datatype "List_F" (Vector.fromList [BuiltinFlat UnitT, Datatype "List" (Vector.singleton $ BuiltinFlat UnitT)])+ :--:> Datatype "#List" (Vector.fromList [BuiltinFlat UnitT, Datatype "List" (Vector.singleton $ BuiltinFlat UnitT)]) :--:> ReturnT (BuiltinFlat IntegerT) nilHandler <- lazyLam nilHandlerTy (AnId <$> lit (AnInteger 0)) consHandler <- lazyLam consHandlerTy (AnId <$> lit (AnInteger 0))@@ -777,7 +860,7 @@ tvA <- boundTyVar (S Z) ix0 vA <- AnArg <$> arg Z ix0 nil <- AnId <$> ctor "List" "Nil" mempty (Vector.singleton (Here tvA))- AnId <$> ctor "List" "Cons" (Vector.fromList [vA, nil]) (Vector.singleton Nowhere)+ AnId <$> ctor "List" "Cons" (Vector.fromList [vA, nil]) Vector.empty scrutinee <- AnArg <$> arg Z ix0 AnId <$> match scrutinee (AnId <$> Vector.fromList [justHandler, nothingHandler]) typeIdTest thonk@@ -787,6 +870,65 @@ maybeToListTy :: ValT AbstractTy maybeToListTy = ThunkT maybeToListCompTy++{- This tests that Opaques don't break the unifier. Arguably it should be in type-applications, but we need a bunch of+ ASG stuff that's not imported there to construct the test, so it is here instead.++ The lambda we construct has the type: Maybe Opaque -> Integer+-}++unifyOpaque :: TestTree+unifyOpaque = runIntroFormTest "unifyOpaque" unifyOpaqueTy $ do+ thonk <- lazyLam unifyOpaqueCompTy $ do+ let nothingHandlerTy = Comp0 $ ReturnT (BuiltinFlat IntegerT)+ justHandlerTy = Comp0 $ dtype "Foo" [] :--:> ReturnT (BuiltinFlat IntegerT)+ nothingHandler <- lazyLam nothingHandlerTy (AnId <$> lit (AnInteger 0))+ justHandler <- lazyLam justHandlerTy (AnId <$> lit (AnInteger 1))+ scrutinee <- AnArg <$> arg Z ix0+ AnId <$> match scrutinee (AnId <$> Vector.fromList [justHandler, nothingHandler])+ typeIdTest thonk+ where+ unifyOpaqueCompTy :: CompT AbstractTy+ unifyOpaqueCompTy = Comp0 $ dtype "Maybe" [dtype "Foo" []] :--:> ReturnT (BuiltinFlat IntegerT)+ unifyOpaqueTy :: ValT AbstractTy+ unifyOpaqueTy = ThunkT unifyOpaqueCompTy++matchOpaque :: TestTree+matchOpaque = runIntroFormTest "matchOpaque" matchOpaqueTy $ do+ thonk <- lazyLam matchOpaqueCompTy $ do+ let iHandlerTy = Comp0 $ BuiltinFlat IntegerT :--:> ReturnT (BuiltinFlat IntegerT)+ bHandlerTy = Comp0 $ BuiltinFlat ByteStringT :--:> ReturnT (BuiltinFlat IntegerT)+ iHandler <- lazyLam iHandlerTy $ AnId <$> lit (AnInteger 0)+ bHandler <- lazyLam bHandlerTy $ AnId <$> lit (AnInteger 1)+ scrutinee <- AnArg <$> arg Z ix0+ AnId <$> match scrutinee (AnId <$> Vector.fromList [iHandler, bHandler])+ typeIdTest thonk+ where+ matchOpaqueCompTy :: CompT AbstractTy+ matchOpaqueCompTy = Comp0 $ dtype "Foo" [] :--:> ReturnT (BuiltinFlat IntegerT)+ matchOpaqueTy :: ValT AbstractTy+ matchOpaqueTy = ThunkT matchOpaqueCompTy++argBugUnitTest :: TestTree+argBugUnitTest = testCase "argBugTest" $ withCompilationSuccessUnit asg (\_ -> pure ())+ where+ intT :: ValT AbstractTy+ intT = BuiltinFlat IntegerT+ asg :: ASGBuilder Id+ asg = lam (Comp1 $ tyvar Z ix0 :--:> ReturnT (tyvar Z ix0)) $ do+ gimmeZ0 <- lam (Comp0 $ intT :--:> ReturnT (tyvar (S Z) ix0)) $ do+ AnArg <$> arg (S Z) ix0+ one <- AnId <$> lit (AnInteger 1)+ AnId <$> app' gimmeZ0 [one]++-- tests for our concretification fix in `app`.+-- We only care that compilation succeeds.++simpleConcretifyUnitTest :: TestTree+simpleConcretifyUnitTest = testCase "simpleConcretify" $ withCompilationSuccessUnit concretifyMinimalBuilder (\_ -> pure ())++excessiveConcretifyUnitTest :: TestTree+excessiveConcretifyUnitTest = testCase "excessiveConcretify" $ withCompilationSuccessUnit concretifyMegaTest (\_ -> pure ()) -- Helpers
+ test/json-conformance/Main.hs view
@@ -0,0 +1,307 @@+{-# LANGUAGE OverloadedLists #-}++module Main (main) where++import Control.Monad (void)+import Covenant.ASG+ ( ASG,+ ASGBuilder,+ CovenantError,+ Id,+ Ref (AnArg, AnId),+ app',+ arg,+ baseFunctorOf,+ builtin2,+ cata,+ ctor,+ ctor',+ dtype,+ err,+ lam,+ lazyLam,+ lit,+ match,+ runASGBuilder,+ )+import Covenant.Constant+ ( AConstant (AString, AnInteger),+ )+import Covenant.DeBruijn (DeBruijn (S, Z))+import Covenant.Index (ix0, ix1)+import Covenant.JSON (deserializeAndValidate_)+import Covenant.Prim (TwoArgFunc (AddInteger, EqualsInteger, SubtractInteger))+import Covenant.Test+ ( conformanceDatatypes1,+ conformanceDatatypes2,+ unsafeMkDatatypeInfos,+ )+import Covenant.Type+ ( AbstractTy,+ BuiltinFlatT (BoolT, IntegerT, StringT),+ CompT (Comp0, Comp1),+ CompTBody (ReturnT, (:--:>)),+ ValT (BuiltinFlat, Datatype),+ tyvar,+ )+import Data.Either (isRight)+import Data.Vector qualified as Vector+import Data.Wedge (Wedge (There))+import Test.Tasty (defaultMain, testGroup)+import Test.Tasty.HUnit (assertBool, testCase)++main :: IO ()+main =+ defaultMain . testGroup "Conformance" $+ [ testCase "conformance1_asg" (assertBool "case 1 compiles to asg" $ isRight conformance_body1),+ testCase "conformance2_asg" (assertBool "case 2 compiles to asg" $ isRight conformance_body2),+ testCase "deserialize_1" (void $ deserializeAndValidate_ "./test/json-conformance/conformance_case_1.json"),+ testCase "deserialize_2" (void $ deserializeAndValidate_ "./test/json-conformance/conformance_case_2.json")+ ]++{- Case 1:++Datatypes:++data Maybe a = Nothing | Just a++data Result e a = Exception e | OK a++data Pair a b = Pair a b++data List a = Nil | Cons a (List a)++Body:++f :: Maybe (Pair Integer Integer) -> List Integer -> Result String Integer+f = \inpMPair inpList ->+ match inpMPair+ (Exception "Input is Nothing")+ (\pair ->+ match+ (\a b ->+ let listSum = cata (\listF -> match listF+ (0)+ (\x r -> x + r)+ )+ in OK (a + b + listSum)+ )+ )++-}++(#+) :: Ref -> Ref -> ASGBuilder Ref+x #+ y = do+ plus <- builtin2 AddInteger+ AnId <$> app' plus [x, y]++(#-) :: Ref -> Ref -> ASGBuilder Ref+x #- y = do+ minus <- builtin2 SubtractInteger+ AnId <$> app' minus [x, y]++(#==) :: Ref -> Ref -> ASGBuilder Ref+x #== y = do+ equals <- builtin2 EqualsInteger+ AnId <$> app' equals [x, y]++conformance_body1 :: Either CovenantError ASG+conformance_body1 =+ runASGBuilder+ (unsafeMkDatatypeInfos conformanceDatatypes1)+ conformance_body1_builder++conformance_body1_builder :: ASGBuilder Id+conformance_body1_builder = lam topLevelTy body+ where+ {- arg1: Maybe (Pair Integer Integer)+ arg2: List Integer+ -}+ body :: ASGBuilder Ref+ body = do+ maybeIntPair <- AnArg <$> arg Z ix0+ nothingHandler' <- nothingHandler+ justHandler' <- justHandler+ AnId <$> match maybeIntPair [AnId nothingHandler', AnId justHandler']++ nothingHandler :: ASGBuilder Id+ nothingHandler = lazyLam nothingHandlerT $ do+ errMsg <- AnId <$> lit (AString "Input is nothing")+ AnId <$> ctor "Result" "Exception" (Vector.singleton errMsg) [There (BuiltinFlat IntegerT)]+ where+ nothingHandlerT :: CompT AbstractTy+ nothingHandlerT = Comp0 $ ReturnT resultT++ justHandler :: ASGBuilder Id+ justHandler = lazyLam justHandlerT $ do+ intPair <- AnArg <$> arg Z ix0+ pairHandler' <- pairHandler+ AnId <$> match intPair [AnId pairHandler']+ where+ justHandlerT :: CompT AbstractTy+ justHandlerT = Comp0 $ intPairT :--:> ReturnT resultT++ pairHandler :: ASGBuilder Id+ pairHandler = lazyLam pairHandlerT $ do+ int1 <- AnArg <$> arg Z ix0+ int2 <- AnArg <$> arg Z ix1+ summedArgs <- int1 #+ int2+ tlListInt <- AnArg <$> arg (S (S Z)) ix1+ summedList <- AnId <$> sumList tlListInt+ finalResult <- summedArgs #+ summedList+ AnId <$> ctor "Result" "OK" [finalResult] [There (BuiltinFlat StringT)]+ where+ pairHandlerT :: CompT AbstractTy+ pairHandlerT = Comp0 $ intT :--:> intT :--:> ReturnT resultT++ sumList :: Ref -> ASGBuilder Id+ sumList listToSum = do+ listF <- baseFunctorOf "List"+ let cataTy = Comp0 $ Datatype listF [intT, intT] :--:> ReturnT intT+ nilHandler <- AnId <$> lit (AnInteger 0)+ consHandler <- lazyLam (Comp0 $ intT :--:> intT :--:> ReturnT intT) $ do+ x <- AnArg <$> arg Z ix0+ y <- AnArg <$> arg Z ix1+ x #+ y+ cata cataTy [nilHandler, AnId consHandler] listToSum+ {-+ sumList :: Ref -> ASGBuilder Id+ sumList listToSum = do+ sumListF' <- AnId <$> sumListF+ cata sumListF' listToSum++ sumListF :: ASGBuilder Id+ sumListF = lazyLam (Comp0 $ listFIntT :--:> ReturnT intT) $ do+ listFInt <- AnArg <$> arg Z ix0+ nilHandler <- lazyLam (Comp0 . ReturnT $ intT) (AnId <$> lit (AnInteger 0))+ consHandler <- lazyLam (Comp0 $ intT :--:> intT :--:> ReturnT intT) $ do+ x <- AnArg <$> arg Z ix0+ y <- AnArg <$> arg Z ix1+ x #+ y+ AnId <$> match listFInt (AnId <$> [nilHandler, consHandler])+ where+ listFIntT :: ValT AbstractTy+ listFIntT = dtype "#List" [intT, intT]+ -}++ intT :: ValT AbstractTy+ intT = BuiltinFlat IntegerT++ stringT :: ValT AbstractTy+ stringT = BuiltinFlat StringT++ intPairT :: ValT AbstractTy+ intPairT = dtype "Pair" [intT, intT]++ maybeIntPairT :: ValT AbstractTy+ maybeIntPairT =+ dtype+ "Maybe"+ [intPairT]++ listIntT :: ValT AbstractTy+ listIntT = dtype "List" [intT]++ resultT :: ValT AbstractTy+ resultT = dtype "Result" [stringT, intT]++ topLevelTy :: CompT AbstractTy+ topLevelTy = Comp0 $ maybeIntPairT :--:> listIntT :--:> ReturnT resultT++{- Case 2:++opaque data Foo = Foo++data Void++data Maybe a = Nothing | Just a++data Pair a b = Pair a b++f :: Maybe (Pair Integer Foo) -> Maybe Boolean+f mabPairIntFoo =+ let g :: forall a. Integer -> a -> Integer+ g n _x = n + n+ in match mabPairIntFoo+ (error "Input is nothing")+ (\pairIntFoo ->+ match pairIntFoo (\n foo ->+ let doubled = g n foo+ zero = doubled - doubled+ in Just (zero == 0)+ )+ )+-}++conformance_body2 :: Either CovenantError ASG+conformance_body2 =+ runASGBuilder+ (unsafeMkDatatypeInfos conformanceDatatypes2)+ conformance_body2_builder++conformance_body2_builder :: ASGBuilder Id+conformance_body2_builder = lam topLevelTy body+ where+ body :: ASGBuilder Ref+ body = do+ maybeIntFooPair <- AnArg <$> arg Z ix0+ g' <- g+ nothingHandler' <- nothingHandler+ justHandler' <- justHandler g'+ AnId <$> match maybeIntFooPair [AnId nothingHandler', AnId justHandler']++ nothingHandler :: ASGBuilder Id+ nothingHandler = lazyLam nothingHandlerT (AnId <$> err)+ where+ nothingHandlerT :: CompT AbstractTy+ nothingHandlerT = Comp0 $ ReturnT maybeBoolT++ justHandler :: Id -> ASGBuilder Id+ justHandler gx = lazyLam justHandlerTy $ do+ intFooPair <- AnArg <$> arg Z ix0+ pairHandler' <- pairHandler gx+ AnId <$> match intFooPair [AnId pairHandler']+ where+ justHandlerTy :: CompT AbstractTy+ justHandlerTy = Comp0 $ pairIntFooT :--:> ReturnT maybeBoolT++ pairHandler :: Id -> ASGBuilder Id+ pairHandler gx = lazyLam pairHandlerTy $ do+ intArg <- AnArg <$> arg Z ix0+ fooArg <- AnArg <$> arg Z ix1+ doubled <- AnId <$> app' gx [intArg, fooArg]+ zero <- doubled #- doubled+ zeroIs0 <- zero #== zero+ AnId <$> ctor' "Maybe" "Just" [zeroIs0]+ where+ pairHandlerTy :: CompT AbstractTy+ pairHandlerTy = Comp0 $ integerT :--:> fooT :--:> ReturnT maybeBoolT++ g :: ASGBuilder Id+ g = lam gTy $ do+ intArg <- AnArg <$> arg Z ix0+ intArg #+ intArg+ where+ gTy :: CompT AbstractTy+ gTy = Comp1 $ integerT :--:> tyvar Z ix0 :--:> ReturnT integerT++ topLevelTy :: CompT AbstractTy+ topLevelTy = Comp0 $ maybePairIntFooT :--:> ReturnT maybeBoolT++ integerT :: forall a. ValT a+ integerT = BuiltinFlat IntegerT++ boolT :: forall a. ValT a+ boolT = BuiltinFlat BoolT++ fooT :: ValT AbstractTy+ fooT = dtype "Foo" []++ pairIntFooT :: ValT AbstractTy+ pairIntFooT = dtype "Pair" [integerT, fooT]++ maybePairIntFooT :: ValT AbstractTy+ maybePairIntFooT = dtype "Maybe" [pairIntFooT]++ maybeBoolT :: ValT AbstractTy+ maybeBoolT = dtype "Maybe" [boolT]
test/primops/Main.hs view
@@ -21,8 +21,8 @@ UnListData, UnMapData ),- SixArgFunc (CaseData, ChooseData),- ThreeArgFunc (CaseList, ChooseList),+ SixArgFunc (ChooseData),+ ThreeArgFunc (ChooseList), TwoArgFunc (ConstrData, EqualsData, MkCons, MkPairData), typeOneArgFunc, typeSixArgFunc,@@ -37,15 +37,13 @@ ) import Covenant.Type ( AbstractTy (BoundAt),- CompT (Comp0),+ CompT, Renamed (Unifiable),- ValT (Datatype, ThunkT),+ ValT (Datatype), arity, boolT, byteStringT, integerT,- pattern ReturnT,- pattern (:--:>), ) import Data.Functor.Classes (liftEq) import Data.Functor.Identity (Identity (Identity))@@ -111,13 +109,11 @@ ], testGroup "Three arguments"- [ testCase "ChooseList" unitChooseList,- testCase "CaseList" unitCaseList+ [ testCase "ChooseList" unitChooseList ], testGroup "Six arguments"- [ testCase "ChooseData" unitChooseData,- testCase "CaseData" unitCaseData+ [ testCase "ChooseData" unitChooseData ] ] ]@@ -248,30 +244,10 @@ in withRenamedVals [listT, byteStringT, byteStringT] $ tryAndApply byteStringT renamedFunT -unitCaseList :: IO ()-unitCaseList = withRenamedComp (typeThreeArgFunc CaseList) $ \renamedFunT ->- let listT = Datatype "List" . Vector.singleton $ integerT- thunkT = ThunkT $ Comp0 $ integerT :--:> listT :--:> ReturnT byteStringT- in withRenamedVals [byteStringT, thunkT, listT] $- tryAndApply byteStringT renamedFunT- unitChooseData :: IO () unitChooseData = withRenamedComp (typeSixArgFunc ChooseData) $ \renamedFunT -> withRenamedVals [dataT, integerT, integerT, integerT, integerT, integerT] $ tryAndApply integerT renamedFunT--unitCaseData :: IO ()-unitCaseData = withRenamedComp (typeSixArgFunc CaseData) $ \renamedFunT ->- let listDataT = Datatype "List" . Vector.singleton $ dataT- pairDataT = Datatype "Pair" . Vector.fromList $ [dataT, dataT]- listPairDataT = Datatype "List" . Vector.singleton $ pairDataT- constrThunkT = ThunkT $ Comp0 $ integerT :--:> listDataT :--:> ReturnT integerT- mapThunkT = ThunkT $ Comp0 $ listPairDataT :--:> ReturnT integerT- listThunkT = ThunkT $ Comp0 $ listDataT :--:> ReturnT integerT- integerThunkT = ThunkT $ Comp0 $ integerT :--:> ReturnT integerT- byteStringThunkT = ThunkT $ Comp0 $ byteStringT :--:> ReturnT integerT- in withRenamedVals [constrThunkT, mapThunkT, listThunkT, integerThunkT, byteStringThunkT, dataT] $- tryAndApply integerT renamedFunT -- Helpers
test/type-applications/Main.hs view
@@ -134,8 +134,8 @@ withRenamedVals mempty excessArgs $ \renamedExcessArgs -> case renamedExcessArgs of [] -> discard -- should be impossible- _ : extraArgs ->- let expected = Left . ExcessArgs renamedIdT . Vector.fromList . fmap Just $ extraArgs+ arg : extraArgs ->+ let expected = Left . ExcessArgs renamedIdT . Vector.fromList . fmap Just $ (arg : extraArgs) actual = checkApp M.empty renamedIdT (fmap Just renamedExcessArgs) in expected === actual where