summaryrefslogtreecommitdiff
path: root/source/Felix/Test/Unit/Backend.hs
diff options
context:
space:
mode:
authoradelon <22380201+adelon@users.noreply.github.com>2026-08-06 17:54:00 +0200
committeradelon <22380201+adelon@users.noreply.github.com>2026-08-06 17:54:00 +0200
commit82328890108bae64b372b8d58620ebc62699de76 (patch)
tree575404c6b425c19259c0ded296f1c8ffb7ff0e2b /source/Felix/Test/Unit/Backend.hs
parent1a25421c2a168d420581358c8733fcd8f36f379b (diff)
Migrate to `Felix` namespaceHEADhotg
Diffstat (limited to 'source/Felix/Test/Unit/Backend.hs')
-rw-r--r--source/Felix/Test/Unit/Backend.hs752
1 files changed, 752 insertions, 0 deletions
diff --git a/source/Felix/Test/Unit/Backend.hs b/source/Felix/Test/Unit/Backend.hs
new file mode 100644
index 0000000..384d21b
--- /dev/null
+++ b/source/Felix/Test/Unit/Backend.hs
@@ -0,0 +1,752 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Felix.Test.Unit.Backend (unitTests) where
+
+import Base hiding (Empty)
+import Felix.Checking.Backend.Problem
+import Felix.Checking.Backend.Tptp
+import Felix.Checking.Core
+import Felix.Checking.Foundation qualified as Foundation
+import Felix.Provers
+import Tptp.UnsortedFirstOrder qualified as Tptp
+
+import Data.Map.Strict qualified as Map
+import Data.Text qualified as Text
+import Data.Vector (Vector)
+import Data.Vector qualified as Vector
+import Numeric.Natural (Natural)
+import Test.Tasty
+import Test.Tasty.HUnit
+
+
+data TestGlobal
+ = FirstOrderPredicate
+ | HigherOrderPredicate
+ deriving (Show, Eq, Ord)
+
+testGlobalType :: TestGlobal -> Maybe CoreType
+testGlobalType = \case
+ FirstOrderPredicate ->
+ Just (TySet `TyArrow` TyProp)
+ HigherOrderPredicate ->
+ Just
+ ((TySet `TyArrow` TyProp)
+ `TyArrow` TyProp)
+
+data TestLocal
+ = ObjectLocal
+ | PredicateLocal
+ deriving (Show, Eq, Ord)
+
+unitTests :: TestTree
+unitTests =
+ testGroup "Typed backend problem"
+ [ testCase
+ "projects proposition equality as equivalence"
+ classifiesPropositionEquality
+ , testCase
+ "projects exact ambient support"
+ projectsExactAmbientSupport
+ , testCase
+ "routes implicit, explicit, and local-only problems"
+ routesCompleteProblems
+ , testCase
+ "admits only checked implicit set constructions"
+ admitsImplicitSetConstructions
+ , testCase
+ "renders checked FOF and TH0 problems"
+ rendersCheckedProblems
+ ]
+
+classifiesPropositionEquality :: Assertion
+classifiesPropositionEquality = do
+ proposition <-
+ checkedProposition
+ Vector.empty
+ (CEq TyProp CFalsum CFalsum)
+ capability <-
+ either
+ (assertFailure . show)
+ pure
+ (classifySupportedProposition
+ testGlobalType
+ proposition)
+ case capability of
+ FofProjectable{} ->
+ pure ()
+ RequiresTh0 exclusions ->
+ assertFailure
+ ("proposition equality was not projected: "
+ <> show exclusions)
+
+projectsExactAmbientSupport :: Assertion
+projectsExactAmbientSupport = do
+ let term :: CanonicalTerm Void
+ term =
+ CForall TySet
+ (CEq TySet
+ (CBound 1)
+ (CBound 3))
+ scoped <-
+ either
+ (assertFailure . show)
+ pure
+ (checkScopedCanonicalCore
+ (const Nothing)
+ [TySet, TySet, TySet]
+ term)
+ projected <-
+ either
+ (assertFailure . show)
+ pure
+ (projectSupportedProposition
+ (const Nothing)
+ (Vector.fromList
+ [ (0 :: Int, TySet)
+ , (1, TySet)
+ , (2, TySet)
+ ])
+ scoped)
+ assertEqual
+ "unused middle support is removed"
+ (Vector.fromList
+ [ (0 :: Int, TySet)
+ , (2, TySet)
+ ])
+ (supportedPropositionSupport projected)
+ assertEqual
+ "indices are remapped below nested binders"
+ (CForall TySet
+ (CEq TySet
+ (CBound 1)
+ (CBound 2)))
+ (supportedPropositionTerm projected)
+
+routesCompleteProblems :: Assertion
+routesCompleteProblems = do
+ fofFact <-
+ checkedBackendFact
+ (0 :: Int)
+ firstOrderClaim
+ th0Fact <-
+ checkedBackendFact
+ (1 :: Int)
+ higherOrderClaim
+ let fofFacts =
+ Vector.singleton fofFact
+ th0Facts =
+ Vector.singleton th0Fact
+ claim <-
+ checkedProposition
+ (Vector.singleton
+ (ObjectLocal, TySet))
+ (CApp
+ (CGlobal FirstOrderPredicate)
+ (CBound 0))
+ firstOrderLocal <-
+ checkedLocalPremise
+ 0
+ "first-order local"
+ claim
+ higherOrderLocalProposition <-
+ checkedProposition
+ (Vector.singleton
+ (PredicateLocal,
+ TySet `TyArrow` TyProp))
+ (CApp
+ (CGlobal HigherOrderPredicate)
+ (CBound 0))
+ higherOrderLocal <-
+ checkedLocalPremise
+ 1
+ "higher-order local"
+ higherOrderLocalProposition
+
+ implicit <-
+ planned
+ fofFacts
+ claim
+ [higherOrderLocal, firstOrderLocal]
+ []
+ FirstOrderLocals
+ ImplicitConstructionJustification
+ assertEqual "implicit route" RouteFof
+ (typedProblemRoute implicit)
+ assertEqual "implicit FOF globals" [0]
+ (typedBackendFactReference
+ <$> toList
+ (typedProblemGlobalPremises
+ implicit))
+ assertEqual "first-order local only" [0]
+ (localPremiseOrdinalValue
+ . typedLocalPremiseOrdinal
+ <$> toList
+ (typedProblemLocalPremises
+ implicit))
+
+ explicitFof <-
+ planned
+ fofFacts
+ claim
+ [higherOrderLocal, firstOrderLocal]
+ []
+ FirstOrderLocals
+ ExplicitHigherOrderJustification
+ assertEqual "explicit FOF route" RouteFof
+ (typedProblemRoute explicitFof)
+ assertEqual "explicit FOF references retain only FOF locals" [0]
+ (localPremiseOrdinalValue
+ . typedLocalPremiseOrdinal
+ <$> toList
+ (typedProblemLocalPremises explicitFof))
+
+ explicitTh0 <-
+ planned
+ th0Facts
+ claim
+ [higherOrderLocal, firstOrderLocal]
+ []
+ CompleteLocals
+ ExplicitHigherOrderJustification
+ assertEqual "explicit TH0 route" RouteTh0
+ (typedProblemRoute explicitTh0)
+ assertEqual "explicit TH0 references retain complete locals" [0, 1]
+ (localPremiseOrdinalValue
+ . typedLocalPremiseOrdinal
+ <$> toList
+ (typedProblemLocalPremises explicitTh0))
+
+ localOnly <-
+ planned
+ Vector.empty
+ claim
+ [higherOrderLocal, firstOrderLocal]
+ []
+ CompleteLocals
+ ExplicitHigherOrderJustification
+ assertEqual "local-only TH0 route" RouteTh0
+ (typedProblemRoute localOnly)
+ assertEqual "local order restored" [0, 1]
+ (localPremiseOrdinalValue
+ . typedLocalPremiseOrdinal
+ <$> toList
+ (typedProblemLocalPremises
+ localOnly))
+ assertEqual
+ "complete ambient local inventory"
+ (Map.fromList
+ [ (ObjectLocal, TySet)
+ , (PredicateLocal,
+ TySet `TyArrow` TyProp)
+ ])
+ (typedProblemLocalTypes localOnly)
+
+ case planTypedProblem
+ testGlobalType
+ Vector.empty
+ claim
+ [firstOrderLocal, firstOrderLocal]
+ []
+ CompleteLocals
+ ExplicitHigherOrderJustification of
+ Left
+ (TypedProblemDuplicateLocalPremiseOrdinal
+ duplicateOrdinal) ->
+ assertEqual
+ "duplicate local ordinal"
+ 0
+ (localPremiseOrdinalValue
+ duplicateOrdinal)
+ result ->
+ assertFailure
+ ("expected duplicate local ordinal error, got "
+ <> showProblemResult result)
+
+ higherOrderClaimProposition <-
+ checkedProposition
+ Vector.empty
+ higherOrderClaim
+ case planTypedProblem
+ testGlobalType
+ fofFacts
+ higherOrderClaimProposition
+ []
+ []
+ FirstOrderLocals
+ ImplicitConstructionJustification of
+ Left
+ TypedProblemExplicitHigherOrderJustificationRequired{} ->
+ pure ()
+ result ->
+ assertFailure
+ ("expected explicit higher-order error, got "
+ <> showProblemResult result)
+ checkedFoundationValue <-
+ either
+ (assertFailure . show)
+ pure
+ Foundation.checkedFoundation
+ case planTypedProblem
+ testGlobalType
+ fofFacts
+ claim
+ []
+ [typedFoundationAuxiliaryInput
+ checkedFoundationValue
+ Foundation.SeparationCharacteristic]
+ FirstOrderLocals
+ ImplicitConstructionJustification of
+ Left
+ TypedProblemExplicitHigherOrderJustificationRequired{} ->
+ pure ()
+ result ->
+ assertFailure
+ ("expected implicit higher-order auxiliary error, got "
+ <> showProblemResult result)
+ unusedContext <-
+ either
+ (assertFailure . show)
+ pure
+ (checkScopedCanonicalCore
+ testGlobalType
+ [TySet `TyArrow` TyProp]
+ firstOrderClaim)
+ case supportedProposition
+ (Vector.singleton
+ (PredicateLocal,
+ TySet `TyArrow` TyProp))
+ unusedContext of
+ Left (UnusedSupportedLocal PredicateLocal) ->
+ pure ()
+ Left err ->
+ assertFailure
+ ("unexpected exact-support error: "
+ <> show err)
+ Right _ ->
+ assertFailure
+ "unused ambient local entered exact support"
+ where
+ planned selectedFacts claim locals auxiliaries localPolicy higherOrderPolicy =
+ either
+ (assertFailure . show)
+ pure
+ (planTypedProblem
+ testGlobalType
+ selectedFacts
+ claim
+ locals
+ auxiliaries
+ localPolicy
+ higherOrderPolicy)
+
+ showProblemResult = \case
+ Left err ->
+ show err
+ Right problem ->
+ show (typedProblemRoute problem)
+
+admitsImplicitSetConstructions :: Assertion
+admitsImplicitSetConstructions = do
+ checkedFoundationValue <-
+ either
+ (assertFailure . show)
+ pure
+ Foundation.checkedFoundation
+ let separation =
+ CApp
+ (CApp
+ (CIntrinsic Sep)
+ (CIntrinsic Empty))
+ (CLam TySet
+ (CApp
+ (CGlobal HigherOrderPredicate)
+ (CLam TySet CFalsum)))
+ separationClaim =
+ CEq TySet separation separation
+ filteredDomain =
+ CApp
+ (CApp
+ (CIntrinsic Sep)
+ (CBound 0))
+ (CLam TySet
+ (CEq TySet (CBound 0) (CBound 0)))
+ innerReplacement =
+ CApp
+ (CApp (CIntrinsic Repl) filteredDomain)
+ (CLam TySet (CBound 0))
+ functionalReplacement =
+ CApp
+ (CIntrinsic FamilyUnion)
+ (CApp
+ (CApp
+ (CIntrinsic Repl)
+ (CIntrinsic Empty))
+ (CLam TySet innerReplacement))
+ replacementClaim =
+ CEq TySet functionalReplacement functionalReplacement
+ replacementTags =
+ [ Foundation.FamilyUnionCharacteristic
+ , Foundation.SeparationCharacteristic
+ , Foundation.ReplacementCharacteristic
+ ]
+ auxiliary tag =
+ typedFoundationAuxiliaryInput
+ checkedFoundationValue tag
+ plan selected claim locals tags =
+ planTypedProblem
+ testGlobalType
+ selected
+ claim
+ locals
+ (auxiliary <$> tags)
+ FirstOrderLocals
+ ImplicitConstructionJustification
+
+ separationProposition <-
+ checkedProposition Vector.empty separationClaim
+ separationProblem <-
+ either
+ (assertFailure . show)
+ pure
+ (plan
+ Vector.empty
+ separationProposition
+ []
+ [Foundation.SeparationCharacteristic])
+ assertEqual "separation implicit route" RouteTh0
+ (typedProblemRoute separationProblem)
+ assertEqual "separation characteristic only"
+ [Foundation.SeparationCharacteristic]
+ (typedProblemAuxiliaryTag
+ <$> toList (typedProblemAuxiliaries separationProblem))
+ assertEqual "separation selects no global premise"
+ 0
+ (Vector.length (typedProblemGlobalPremises separationProblem))
+
+ replacementProposition <-
+ checkedProposition Vector.empty replacementClaim
+ replacementProblem <-
+ either
+ (assertFailure . show)
+ pure
+ (plan
+ Vector.empty
+ replacementProposition
+ []
+ replacementTags)
+ assertEqual "functional replacement implicit route" RouteTh0
+ (typedProblemRoute replacementProblem)
+ assertEqual "functional replacement exact helper set"
+ replacementTags
+ (typedProblemAuxiliaryTag
+ <$> toList (typedProblemAuxiliaries replacementProblem))
+
+ firstOrderProposition <-
+ checkedProposition Vector.empty firstOrderClaim
+ firstOrderLocal <-
+ checkedLocalPremise 0 "first-order" firstOrderProposition
+ separationLocal <-
+ checkedLocalPremise 2 "separation" separationProposition
+ unrelatedLocalProposition <-
+ checkedProposition
+ (Vector.singleton
+ (PredicateLocal, TySet `TyArrow` TyProp))
+ (CApp
+ (CGlobal HigherOrderPredicate)
+ (CBound 0))
+ unrelatedLocal <-
+ checkedLocalPremise 1 "unrelated higher-order" unrelatedLocalProposition
+ separationWithLocals <-
+ either
+ (assertFailure . show)
+ pure
+ (plan
+ Vector.empty
+ separationProposition
+ [unrelatedLocal, firstOrderLocal]
+ [Foundation.SeparationCharacteristic])
+ assertEqual "inline separation keeps unrelated HO local out" RouteTh0
+ (typedProblemRoute separationWithLocals)
+ assertEqual "inline separation retains only FOF local" [0]
+ ( localPremiseOrdinalValue
+ . typedLocalPremiseOrdinal
+ <$> toList (typedProblemLocalPremises separationWithLocals)
+ )
+ assertEqual "excluded HO local adds no auxiliary"
+ [Foundation.SeparationCharacteristic]
+ (typedProblemAuxiliaryTag
+ <$> toList (typedProblemAuxiliaries separationWithLocals))
+ localProblem <-
+ either
+ (assertFailure . show)
+ pure
+ (plan
+ Vector.empty
+ firstOrderProposition
+ [unrelatedLocal, separationLocal, firstOrderLocal]
+ [])
+ assertEqual "implicit construction local remains excluded" RouteFof
+ (typedProblemRoute localProblem)
+ assertEqual "unrelated higher-order local remains unselected"
+ [0]
+ ( localPremiseOrdinalValue
+ . typedLocalPremiseOrdinal
+ <$> toList (typedProblemLocalPremises localProblem)
+ )
+
+ higherOrderFact <- checkedBackendFact (1 :: Int) higherOrderClaim
+ expectImplicitHigherOrderRejection
+ "implicit higher-order global remains forbidden"
+ (plan
+ (Vector.singleton higherOrderFact)
+ separationProposition
+ []
+ [Foundation.SeparationCharacteristic])
+
+ ordinaryHigherOrder <-
+ checkedProposition Vector.empty
+ (CEq TySet
+ (CApp
+ (CIntrinsic SetChoose)
+ (CLam TySet CFalsum))
+ (CIntrinsic Empty))
+ expectImplicitHigherOrderRejection
+ "ordinary implicit higher-order target remains forbidden"
+ (plan Vector.empty ordinaryHigherOrder [] [])
+ mixedHigherOrder <-
+ checkedProposition Vector.empty
+ (CImp
+ separationClaim
+ (supportedPropositionTerm ordinaryHigherOrder))
+ expectImplicitHigherOrderRejection
+ "construction does not admit another higher-order intrinsic"
+ (plan
+ Vector.empty
+ mixedHigherOrder
+ []
+ [Foundation.SeparationCharacteristic])
+
+ expectImplicitHigherOrderRejection
+ "auxiliary tag alone grants no construction permission"
+ (plan
+ Vector.empty
+ firstOrderProposition
+ []
+ [Foundation.SeparationCharacteristic])
+ where
+ expectImplicitHigherOrderRejection label = \case
+ Left TypedProblemExplicitHigherOrderJustificationRequired{} ->
+ pure ()
+ Left err ->
+ assertFailure (label <> ": unexpected error " <> show err)
+ Right problem ->
+ assertFailure
+ (label <> ": unexpectedly routed "
+ <> show (typedProblemRoute problem))
+
+rendersCheckedProblems :: Assertion
+rendersCheckedProblems = do
+ fofFact <-
+ checkedBackendFact
+ (0 :: Int)
+ firstOrderClaim
+ th0Fact <-
+ checkedBackendFact
+ (1 :: Int)
+ higherOrderClaim
+ claim <-
+ checkedProposition
+ (Vector.singleton
+ (ObjectLocal, TySet))
+ (CApp
+ (CGlobal FirstOrderPredicate)
+ (CBound 0))
+ fofProblem <-
+ planned
+ (Vector.singleton fofFact)
+ claim
+ FirstOrderLocals
+ ImplicitConstructionJustification
+ th0Problem <-
+ planned
+ (Vector.singleton th0Fact)
+ claim
+ CompleteLocals
+ ExplicitHigherOrderJustification
+ preparedFof <-
+ either
+ (assertFailure . show)
+ pure
+ (prepareTypedTptpProblem
+ fofProblem)
+ preparedTh0 <-
+ either
+ (assertFailure . show)
+ pure
+ (prepareTypedTptpProblem
+ th0Problem)
+ proverTask <-
+ either
+ (assertFailure . show)
+ pure
+ (prepareTypedProverTask
+ DirectTask
+ th0Problem)
+ assertEqual "FOF route" RouteFof
+ (preparedTypedTptpRoute preparedFof)
+ assertBool "FOF formulas"
+ ("fof(tg_h0,axiom,"
+ `Text.isInfixOf`
+ preparedTypedTptpText
+ preparedFof)
+ assertBool "FOF has no TH0 declarations"
+ (not
+ ("thf("
+ `Text.isInfixOf`
+ preparedTypedTptpText
+ preparedFof))
+ assertEqual "TH0 route" RouteTh0
+ (preparedTypedTptpRoute preparedTh0)
+ assertEqual "TH0 request dialect"
+ VerificationTh0
+ (preparedVerificationDialect
+ (preparedTypedProverRequest
+ proverTask))
+ assertEqual "request preserves exact prepared text"
+ (preparedTypedTptpText preparedTh0)
+ (preparedVerificationText
+ (preparedTypedProverRequest
+ proverTask))
+ for_
+ [ "thf(tg_h0,axiom,"
+ , "^ [V0:$i]"
+ , "thf(tg_q0,conjecture,"
+ ]
+ \fragment ->
+ assertBool
+ ("TH0 contains " <> Text.unpack fragment)
+ (fragment
+ `Text.isInfixOf`
+ preparedTypedTptpText
+ preparedTh0)
+ for_
+ (Map.keys
+ (preparedTypedTptpNameOrigins
+ preparedTh0))
+ \target ->
+ assertBool
+ ("valid generated name " <> Text.unpack target)
+ (if "V" `Text.isPrefixOf` target
+ then Tptp.isProperVariable target
+ else Tptp.isProperAtomicWord target)
+ where
+ planned selectedFacts claim localPolicy higherOrderPolicy =
+ either
+ (assertFailure . show)
+ pure
+ (planTypedProblem
+ testGlobalType
+ selectedFacts
+ claim
+ []
+ []
+ localPolicy
+ higherOrderPolicy)
+
+firstOrderClaim :: CanonicalTerm TestGlobal
+firstOrderClaim =
+ CApp
+ (CGlobal FirstOrderPredicate)
+ (CIntrinsic Empty)
+
+higherOrderClaim :: CanonicalTerm TestGlobal
+higherOrderClaim =
+ CApp
+ (CGlobal HigherOrderPredicate)
+ (CLam TySet
+ (CApp
+ (CGlobal FirstOrderPredicate)
+ (CBound 0)))
+
+checkedProposition
+ :: Vector (TestLocal, CoreType)
+ -> CanonicalTerm TestGlobal
+ -> IO
+ (SupportedProposition
+ TestLocal
+ TestGlobal)
+checkedProposition support term = do
+ checked <-
+ either
+ (assertFailure . show)
+ pure
+ (checkScopedCanonicalCore
+ testGlobalType
+ (snd <$> Vector.toList support)
+ term)
+ either
+ (assertFailure . show)
+ pure
+ (supportedProposition support checked)
+
+checkedClosedProposition
+ :: CanonicalTerm TestGlobal
+ -> IO
+ (SupportedProposition
+ Void
+ TestGlobal)
+checkedClosedProposition term = do
+ checked <-
+ either
+ (assertFailure . show)
+ pure
+ (checkScopedCanonicalCore
+ testGlobalType
+ []
+ term)
+ either
+ (assertFailure . show)
+ pure
+ (supportedProposition
+ Vector.empty
+ checked)
+
+checkedBackendFact
+ :: ref
+ -> CanonicalTerm TestGlobal
+ -> IO (TypedBackendFact ref TestGlobal)
+checkedBackendFact reference term = do
+ proposition <-
+ checkedClosedProposition term
+ capability <-
+ either
+ (assertFailure . show)
+ pure
+ (classifySupportedProposition
+ testGlobalType
+ proposition)
+ pure
+ (typedBackendFact
+ reference
+ proposition
+ capability)
+
+checkedLocalPremise
+ :: Natural
+ -> Text
+ -> SupportedProposition TestLocal TestGlobal
+ -> IO
+ (TypedLocalPremise
+ TestLocal
+ Text
+ TestGlobal)
+checkedLocalPremise ordinal premiseOrigin proposition =
+ either
+ (assertFailure . show)
+ pure
+ (typedLocalPremise
+ testGlobalType
+ (localPremiseOrdinal ordinal)
+ premiseOrigin
+ proposition)