Download test/Test/Core/Positivity.hs from Snapkitty/assertica: direct link, hf CLI and curl.
- Browser
- Download file 13.1 kB
-
https://huggingface.co/Snapkitty/assertica/resolve/main/test/Test/Core/Positivity.hs
- Command line
-
hf download hf://Snapkitty/assertica/test/Test/Core/Positivity.hs
-
curl -L -o Positivity.hs https://huggingface.co/Snapkitty/assertica/resolve/main/test/Test/Core/Positivity.hs
13.1 kB
| {-| | |
| Module : Test.Core.Positivity | |
| Description : Tests for positivity checking (Agent 4B) | |
| -} | |
| module Test.Core.Positivity | |
| ( tests | |
| ) where | |
| import Test.Tasty | |
| import Test.Tasty.HUnit | |
| import qualified Data.Set as Set | |
| import qualified Data.Text as T | |
| import Assertica.Core.AST | |
| import Assertica.Core.Positivity | |
| -- ============================================================================ | |
| -- TEST SUITE | |
| -- ============================================================================ | |
| tests :: TestTree | |
| tests = testGroup "Assertica.Core.Positivity" | |
| [ testGroup "Basic Positivity" | |
| [ testCase "Type with no parameters needs no checking" testNoParams | |
| , testCase "Strictly positive list type" testListPositive | |
| , testCase "Strictly positive tree type" testTreePositive | |
| ] | |
| , testGroup "Negative Position Detection" | |
| [ testCase "Function domain is negative position" testFunctionDomainNegative | |
| , testCase "Type parameter in function domain rejected" testParamInDomainRejected | |
| , testCase "Nested function domain rejected" testNestedFunctionNegative | |
| ] | |
| , testGroup "Constructor Analysis" | |
| [ testCase "Multiple constructors checked" testMultipleConstructors | |
| , testCase "Nested positive occurrences accepted" testNestedPositive | |
| , testCase "Mixed positive and negative rejected" testMixedPositiveNegative | |
| ] | |
| , testGroup "Type Parameter Tracking" | |
| [ testCase "Occurrence analysis finds type variables" testOccurrenceAnalysis | |
| , testCase "Analyze position context" testPositionContext | |
| , testCase "Nested type structures" testNestedTypeStructures | |
| ] | |
| , testGroup "Practical Examples" | |
| [ testCase "List a is strictly positive" testListStrictlyPositive | |
| , testCase "Tree a is strictly positive" testTreeStrictlyPositive | |
| , testCase "BadType (a -> Int) rejected" testBadTypeRejected | |
| , testCase "BadList with function in arg rejected" testBadListRejected | |
| ] | |
| , testGroup "Mutual/Recursive Inductive Types" | |
| [ testCase "Mutual positive types accepted" testMutualPositive | |
| , testCase "Mutual with negative occurrence rejected" testMutualWithNegative | |
| ] | |
| , testGroup "Error Messages" | |
| [ testCase "Non-positive occurrence message" testNonPositiveMessage | |
| , testCase "Function domain message" testFunctionDomainMessage | |
| , testCase "Nested negative message" testNestedNegativeMessage | |
| ] | |
| ] | |
| -- ============================================================================ | |
| -- HELPER FUNCTIONS | |
| -- ============================================================================ | |
| {-| Create a simple variable. -} | |
| var :: Text -> Var | |
| var name = Var name 0 | |
| {-| Create a qualified name. -} | |
| qn :: Text -> QName | |
| qn name = QName [] name | |
| {-| Create a binder. -} | |
| binder :: Text -> Maybe Type -> Binder | |
| binder name mty = Binder (var name) mty | |
| {-| Create a type variable. -} | |
| tyVar :: Text -> Type | |
| tyVar name = TyVar (var name) | |
| {-| Create a type constructor application. -} | |
| tyConst :: Text -> [Type] -> Type | |
| tyConst name args = TyConst (qn name) args | |
| {-| Create a function type. -} | |
| tyFun :: Type -> Type -> Type | |
| tyFun = TyFun | |
| -- ============================================================================ | |
| -- BASIC POSITIVITY TESTS | |
| -- ============================================================================ | |
| testNoParams :: Assertion | |
| testNoParams = do | |
| -- data Nat = Zero | Succ Nat (no type parameters) | |
| let params = [] | |
| ctors = [tyConst "Nat" []] | |
| case checkPositivity "Nat" params ctors of | |
| Right () -> return () | |
| Left err -> assertFailure $ "Nat should be positive: " ++ prettyPosError err | |
| testListPositive :: Assertion | |
| testListPositive = do | |
| -- data List a = Nil | Cons a (List a) | |
| let a = var "a" | |
| params = [a] | |
| -- Cons argument types: [a, List a] | |
| ctors = [tyVar "a", tyConst "List" [tyVar "a"]] | |
| case checkPositivity "List" params ctors of | |
| Right () -> return () | |
| Left err -> assertFailure $ "List should be positive: " ++ prettyPosError err | |
| testTreePositive :: Assertion | |
| testTreePositive = do | |
| -- data Tree a = Leaf a | Node (Tree a) (Tree a) | |
| let a = var "a" | |
| params = [a] | |
| -- Constructor args: [a] for Leaf, [Tree a, Tree a] for Node | |
| ctors = [tyVar "a", tyConst "Tree" [tyVar "a"], tyConst "Tree" [tyVar "a"]] | |
| case checkPositivity "Tree" params ctors of | |
| Right () -> return () | |
| Left err -> assertFailure $ "Tree should be positive: " ++ prettyPosError err | |
| -- ============================================================================ | |
| -- NEGATIVE POSITION DETECTION TESTS | |
| -- ============================================================================ | |
| testFunctionDomainNegative :: Assertion | |
| testFunctionDomainNegative = do | |
| -- Type parameter appears in function domain: a -> Int | |
| let a = var "a" | |
| params = [a] | |
| -- Argument type: a -> Int | |
| ctors = [tyFun (tyVar "a") (tyConst "Int" [])] | |
| case checkPositivity "Bad" params ctors of | |
| Left (TypeVarInFunctionDomain _) -> return () -- Should reject | |
| Left err -> assertFailure $ "Wrong error type: " ++ prettyPosError err | |
| Right () -> assertFailure "Function domain should be rejected" | |
| testParamInDomainRejected :: Assertion | |
| testParamInDomainRejected = do | |
| -- data BadType a = Bad (a -> Int) | |
| let a = var "a" | |
| params = [a] | |
| ctors = [tyFun (tyVar "a") (tyConst "Int" [])] | |
| case checkPositivity "BadType" params ctors of | |
| Left (TypeVarInFunctionDomain "a") -> return () | |
| _ -> assertFailure "Should reject a in function domain" | |
| testNestedFunctionNegative :: Assertion | |
| testNestedFunctionNegative = do | |
| -- data BadList a = Cons a (List (a -> Int)) | |
| let a = var "a" | |
| params = [a] | |
| -- (a -> Int) is in a positive position but contains a in negative | |
| listOfFunc = tyConst "List" [tyFun (tyVar "a") (tyConst "Int" [])] | |
| ctors = [tyVar "a", listOfFunc] | |
| case checkPositivity "BadList" params ctors of | |
| Left (NestedNegativeOccurrence "a" _) -> return () | |
| _ -> assertFailure "Should detect nested negative occurrence" | |
| -- ============================================================================ | |
| -- CONSTRUCTOR ANALYSIS TESTS | |
| -- ============================================================================ | |
| testMultipleConstructors :: Assertion | |
| testMultipleConstructors = do | |
| -- data List a = Nil | Cons a (List a) | |
| -- Both constructors must be checked | |
| let a = var "a" | |
| params = [a] | |
| -- Nil args, Cons args | |
| ctors = [tyConst "List" [tyVar "a"]] | |
| case checkPositivity "List" params ctors of | |
| Right () -> return () | |
| Left err -> assertFailure $ "Multiple constructors should all pass: " ++ prettyPosError err | |
| testNestedPositive :: Assertion | |
| testNestedPositive = do | |
| -- Nested type constructor applications in positive positions | |
| -- data Okay a = OK (List (List a)) | |
| let a = var "a" | |
| params = [a] | |
| nestedList = tyConst "List" [tyConst "List" [tyVar "a"]] | |
| ctors = [nestedList] | |
| case checkPositivity "Okay" params ctors of | |
| Right () -> return () | |
| Left err -> assertFailure $ "Nested positive should pass: " ++ prettyPosError err | |
| testMixedPositiveNegative :: Assertion | |
| testMixedPositiveNegative = do | |
| -- data Mixed a = Good a | Bad (a -> Int) | |
| -- First constructor is fine, second violates positivity | |
| let a = var "a" | |
| params = [a] | |
| ctors = [tyVar "a", tyFun (tyVar "a") (tyConst "Int" [])] | |
| case checkPositivity "Mixed" params ctors of | |
| Left _ -> return () -- Should reject due to negative occurrence | |
| Right () -> assertFailure "Should reject negative occurrence" | |
| -- ============================================================================ | |
| -- TYPE PARAMETER TRACKING TESTS | |
| -- ============================================================================ | |
| testOccurrenceAnalysis :: Assertion | |
| testOccurrenceAnalysis = do | |
| -- Analyze where a type variable appears | |
| let params = Set.singleton "a" | |
| ty = tyFun (tyVar "a") (tyConst "List" [tyVar "a"]) | |
| let occs = analyzeTypeOccurrences params ty | |
| assertBool "Should find multiple occurrences of a" (length occs >= 1) | |
| let hasNeg = any (\o -> occPosition o == Negative) occs | |
| assertBool "Should find negative occurrence in domain" hasNeg | |
| testPositionContext :: Assertion | |
| testPositionContext = do | |
| -- Verify position tracking | |
| let params = Set.singleton "a" | |
| ty = tyConst "List" [tyVar "a"] | |
| let occs = analyzeTypeOccurrences params ty | |
| case occs of | |
| [TypeOccurrence _ pos _] -> | |
| assertEqual "Should be in positive position" pos Positive | |
| _ -> assertFailure "Should find exactly one occurrence" | |
| testNestedTypeStructures :: Assertion | |
| testNestedTypeStructures = do | |
| -- Complex nested type: List (Tree (a -> Int)) | |
| let params = Set.singleton "a" | |
| negFunc = tyFun (tyVar "a") (tyConst "Int" []) | |
| tree = tyConst "Tree" [negFunc] | |
| list = tyConst "List" [tree] | |
| let occs = analyzeTypeOccurrences params list | |
| let hasNeg = any (\o -> occPosition o == Negative) occs | |
| assertBool "Should track nested negative position" hasNeg | |
| -- ============================================================================ | |
| -- PRACTICAL EXAMPLE TESTS | |
| -- ============================================================================ | |
| testListStrictlyPositive :: Assertion | |
| testListStrictlyPositive = do | |
| -- data List a = Nil | Cons a (List a) | |
| let a = var "a" | |
| params = [a] | |
| nilCtors = [] -- Nil has no arguments | |
| consCtors = [tyVar "a", tyConst "List" [tyVar "a"]] | |
| allCtors = nilCtors ++ consCtors | |
| case checkPositivity "List" params allCtors of | |
| Right () -> return () | |
| Left err -> assertFailure $ "List should be strictly positive: " ++ prettyPosError err | |
| testTreeStrictlyPositive :: Assertion | |
| testTreeStrictlyPositive = do | |
| -- data Tree a = Leaf a | Node (Tree a) (Tree a) | |
| let a = var "a" | |
| params = [a] | |
| leafCtors = [tyVar "a"] | |
| nodeCtors = [tyConst "Tree" [tyVar "a"], tyConst "Tree" [tyVar "a"]] | |
| allCtors = leafCtors ++ nodeCtors | |
| case checkPositivity "Tree" params allCtors of | |
| Right () -> return () | |
| Left err -> assertFailure $ "Tree should be strictly positive: " ++ prettyPosError err | |
| testBadTypeRejected :: Assertion | |
| testBadTypeRejected = do | |
| -- data BadType a = Bad (a -> Int) | |
| let a = var "a" | |
| params = [a] | |
| badCtors = [tyFun (tyVar "a") (tyConst "Int" [])] | |
| case checkPositivity "BadType" params badCtors of | |
| Left _ -> return () -- Should be rejected | |
| Right () -> assertFailure "BadType should be rejected" | |
| testBadListRejected :: Assertion | |
| testBadListRejected = do | |
| -- data BadList a = Cons a (BadList (a -> Int)) | |
| let a = var "a" | |
| params = [a] | |
| funcType = tyFun (tyVar "a") (tyConst "Int" []) | |
| badListArg = tyConst "BadList" [funcType] | |
| badCtors = [tyVar "a", badListArg] | |
| case checkPositivity "BadList" params badCtors of | |
| Left _ -> return () -- Should be rejected | |
| Right () -> assertFailure "BadList should be rejected" | |
| -- ============================================================================ | |
| -- MUTUAL/RECURSIVE INDUCTIVE TYPES TESTS | |
| -- ============================================================================ | |
| testMutualPositive :: Assertion | |
| testMutualPositive = do | |
| -- data Even = Zero | SuccO Odd | |
| -- data Odd = SuccE Even | |
| let defs = | |
| [ ("Even", [], [tyConst "Odd" []]) | |
| , ("Odd", [], [tyConst "Even" []]) | |
| ] | |
| case checkPositivityMulti defs of | |
| Right () -> return () | |
| Left err -> assertFailure $ "Mutual types should pass: " ++ prettyPosError err | |
| testMutualWithNegative :: Assertion | |
| testMutualWithNegative = do | |
| -- data Even = Zero | SuccO (Odd -> Int) -- NEGATIVE! | |
| -- data Odd = SuccE Even | |
| let a = var "a" | |
| defs = | |
| [ ("Even", [a], [tyFun (tyConst "Odd" []) (tyConst "Int" [])]) | |
| , ("Odd", [a], [tyConst "Even" []]) | |
| ] | |
| case checkPositivityMulti defs of | |
| Left _ -> return () -- Should be rejected | |
| Right () -> assertFailure "Mutual with negative should be rejected" | |
| -- ============================================================================ | |
| -- ERROR MESSAGE TESTS | |
| -- ============================================================================ | |
| testNonPositiveMessage :: Assertion | |
| testNonPositiveMessage = do | |
| let err = NonPositiveOccurrence "a" "in function domain" | |
| let msg = prettyPosError err | |
| assertBool "Error should mention type parameter" $ "a" `elem` words msg | |
| assertBool "Error should mention non-positive" $ "non-positive" `elem` words msg | |
| testFunctionDomainMessage :: Assertion | |
| testFunctionDomainMessage = do | |
| let err = TypeVarInFunctionDomain "a" | |
| let msg = prettyPosError err | |
| assertBool "Error should mention function domain" $ "domain" `elem` words msg | |
| assertBool "Error should mention parameter a" $ "a" `elem` words msg | |
| testNestedNegativeMessage :: Assertion | |
| testNestedNegativeMessage = do | |
| let err = NestedNegativeOccurrence "a" "in List (a -> Int)" | |
| let msg = prettyPosError err | |
| assertBool "Error should mention nested" $ "nested" `elem` words msg | |
| assertBool "Error should mention negative" $ "negative" `elem` words msg | |