summaryrefslogtreecommitdiff
path: root/source/Test/Unit/Core.hs
diff options
context:
space:
mode:
authoradelon <22380201+adelon@users.noreply.github.com>2026-07-28 10:55:56 +0200
committeradelon <22380201+adelon@users.noreply.github.com>2026-07-28 10:55:56 +0200
commit44b7226a6529eaf94c4234a8e27fd7732b95232f (patch)
treec2821ee5c5ece457b69cdff5c8915d09a6c5a07f /source/Test/Unit/Core.hs
parent60b4b379d4532c5a688f2dbf1e05736c636059c5 (diff)
Freeze checked HOL terms canonically
Diffstat (limited to 'source/Test/Unit/Core.hs')
-rw-r--r--source/Test/Unit/Core.hs189
1 files changed, 189 insertions, 0 deletions
diff --git a/source/Test/Unit/Core.hs b/source/Test/Unit/Core.hs
new file mode 100644
index 0000000..cb32323
--- /dev/null
+++ b/source/Test/Unit/Core.hs
@@ -0,0 +1,189 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module Test.Unit.Core (unitTests) where
+
+import Base
+import Checking.Core
+
+import Control.DeepSeq (NFData(..), force)
+import Hedgehog
+import Hedgehog.Gen qualified as Gen
+import Hedgehog.Range qualified as Range
+import Test.Tasty
+import Test.Tasty.HUnit hiding (assert)
+import Test.Tasty.Hedgehog (testPropertyNamed)
+
+
+data TestGlobal = TestGlobal
+ deriving (Show, Eq, Ord)
+
+instance NFData TestGlobal where
+ rnf TestGlobal =
+ ()
+
+testGlobalType :: TestGlobal -> Maybe CoreType
+testGlobalType TestGlobal =
+ Just TySet
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Checked core"
+ [ testCase
+ "freezes explicit binder positions"
+ freezesExplicitBinderPositions
+ , testCase
+ "freezes generalized substitution without capture"
+ freezesGeneralizedSubstitution
+ , testCase
+ "rejects ill-typed and open terms"
+ rejectsInvalidTerms
+ , testPropertyNamed
+ "optimized freeze agrees with bounded reference"
+ "prop_freezeAgreesWithReference"
+ prop_freezeAgreesWithReference
+ ]
+
+freezesExplicitBinderPositions :: Assertion
+freezesExplicitBinderPositions = do
+ let x = 0 :: Int
+ y = 1 :: Int
+ freeze body = do
+ checked <-
+ either
+ (assertFailure . show)
+ pure
+ (checkClosedCore
+ testGlobalType
+ (coreLambda TySet x
+ (coreLambda TyProp y body)))
+ either
+ (assertFailure . show)
+ (pure . frozenCoreTerm)
+ (freezeClosed checked)
+ nearest <-
+ freeze (coreLocal y)
+ outer <-
+ freeze (coreLocal x)
+ global <-
+ freeze (coreGlobal TestGlobal)
+ assertEqual
+ "nearest binder"
+ (CLam TySet (CLam TyProp (CBound 0)))
+ nearest
+ assertEqual
+ "outer binder"
+ (CLam TySet (CLam TyProp (CBound 1)))
+ outer
+ assertEqual
+ "global with both vacuous binders"
+ (CLam TySet (CLam TyProp (CGlobal TestGlobal)))
+ global
+
+freezesGeneralizedSubstitution :: Assertion
+freezesGeneralizedSubstitution = do
+ let outer = 0 :: Int
+ inner = 1 :: Int
+ placeholder = 2 :: Int
+ innerTerm =
+ coreLambda TySet inner (coreLocal placeholder)
+ substituted =
+ innerTerm >>= \local ->
+ if local == placeholder
+ then coreLocal outer
+ else coreLocal local
+ term =
+ coreLambda TyProp outer substituted
+ checked <-
+ either
+ (assertFailure . show)
+ pure
+ (checkClosedCore testGlobalType term)
+ optimized <-
+ either
+ (assertFailure . show)
+ pure
+ (freezeClosed checked)
+ reference <-
+ either
+ (assertFailure . show)
+ pure
+ (referenceFreezeClosed checked)
+ assertEqual "reference result" reference optimized
+ assertEqual
+ "outer variable remains outside the inner binder"
+ (CLam TyProp (CLam TySet (CBound 1)))
+ (frozenCoreTerm optimized)
+
+rejectsInvalidTerms :: Assertion
+rejectsInvalidTerms = do
+ assertEqual
+ "free local"
+ (Left UnboundCoreLocal)
+ (checkedCoreType
+ <$> checkClosedCore
+ testGlobalType
+ (coreLocal (0 :: Int)))
+ assertEqual
+ "application argument"
+ (Left
+ (ApplicationArgumentTypeMismatch
+ TySet
+ TyProp))
+ (checkedCoreType
+ <$> checkClosedCore
+ testGlobalType
+ (coreApply
+ (coreIntrinsic FamilyUnion)
+ coreFalsum))
+ assertEqual
+ "equality operand"
+ (Left
+ (EqualityOperandTypeMismatch
+ TySet
+ TyProp))
+ (checkedCoreType
+ <$> checkClosedCore
+ testGlobalType
+ (coreEquality
+ TySet
+ coreFalsum
+ (coreGlobal TestGlobal)))
+
+prop_freezeAgreesWithReference :: Property
+prop_freezeAgreesWithReference = property do
+ depth <-
+ forAll (Gen.int (Range.linear 1 10))
+ binderTypes <-
+ forAll
+ (Gen.list
+ (Range.singleton depth)
+ (Gen.element
+ [ TyProp
+ , TySet
+ , TySet `TyArrow` TySet
+ ]))
+ selected <-
+ forAll
+ (Gen.maybe
+ (Gen.int (Range.linear 0 (depth - 1))))
+ let binders =
+ zip [0 :: Int ..] binderTypes
+ body =
+ maybe
+ (coreGlobal TestGlobal)
+ coreLocal
+ selected
+ term =
+ foldr
+ (\(local, binderType) ->
+ coreLambda binderType local)
+ body
+ binders
+ checked <-
+ evalEither
+ (checkClosedCore testGlobalType term)
+ optimized <-
+ evalEither (force <$> freezeClosed checked)
+ reference <-
+ evalEither (force <$> referenceFreezeClosed checked)
+ optimized === reference