File size: 13,233 Bytes
a10b189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
{-# LANGUAGE OverloadedStrings #-}

{-|
Module      : Test.Core.Pattern
Description : Unit tests for the pattern compiler (Agent 4A)
Copyright   : (c) 2026 Ahmad Ali Parr
License     : BSL-1.0

Tests for:
- Pattern AST construction and validation
- Constructor database queries
- Exhaustiveness checking
- Pattern compilation to core Case expressions
- Type-aware pattern matching
- Error diagnostics
-}

module Test.Core.Pattern
  ( tests
  ) where

import Test.Tasty
import Test.Tasty.HUnit
import qualified Data.Set as Set
import qualified Data.Map.Strict as Map
import Data.Text (Text)
import qualified Data.Text as T

import Assertica.Core.AST
import Assertica.Core.Pattern

-- ============================================================================
-- HELPER FUNCTIONS
-- ============================================================================

{-| Create a simple QName from module path and local name.
-}
qname :: [Text] -> Text -> QName
qname = QName

{-| Create a variable with given name and ID.
-}
var :: Text -> Int -> Var
var name id = Var name id

{-| Create a type constant (e.g., List, Bool, Int).
-}
typeCon :: [Text] -> Text -> Type
typeCon mod name = TyConst (qname mod name) []

{-| Create a type constant with arguments.
-}
typeConApp :: [Text] -> Text -> [Type] -> Type
typeConApp mod name args = TyConst (qname mod name) args

-- ============================================================================
-- UNIT TESTS
-- ============================================================================

tests :: TestTree
tests = testGroup "Assertica.Core.Pattern"
  [ testGroup "Constructor Database"
      [ testCase "Empty database" testEmptyDB
      , testCase "Add constructor" testAddConstructor
      , testCase "Query constructors" testQueryConstructors
      , testCase "Query unknown type" testQueryUnknownType
      ]

  , testGroup "Pattern Compilation"
      [ testCase "Compile variable pattern" testCompileVarPattern
      , testCase "Compile wildcard pattern" testCompileWildcardPattern
      , testCase "Compile constructor pattern" testCompileConstrPattern
      , testCase "Compile nested patterns" testCompileNestedPatterns
      , testCase "Extract pattern bindings" testExtractBindings
      ]

  , testGroup "Exhaustiveness Checking"
      [ testCase "Default clause is exhaustive" testDefaultExhaustive
      , testCase "Empty patterns with default" testEmptyPatternsWithDefault
      , testCase "Single wildcard is exhaustive" testWildcardExhaustive
      ]

  , testGroup "First Milestone: List Pattern Matching"
      [ testCase "List type with Nil and Cons" testListTypeDB
      , testCase "Simple list patterns compile" testSimpleListPatterns
      , testCase "List patterns are exhaustive" testListPatternsExhaustive
      ]

  , testGroup "Pattern Type Inference"
      [ testCase "Infer variable pattern type" testInferVarType
      , testCase "Infer constructor pattern type" testInferConstrType
      ]

  , testGroup "Error Handling"
      [ testCase "Unknown constructor error" testUnknownConstructorError
      , testCase "Arity mismatch error" testArityMismatchError
      ]

  , testGroup "Pattern Utilities"
      [ testCase "Is wildcard pattern" testIsWildcardPattern
      , testCase "Extract constructors from pattern" testPatternConstructors
      , testCase "Check redundant patterns" testRedundantPatterns
      ]
  ]

-- ============================================================================
-- CONSTRUCTOR DATABASE TESTS
-- ============================================================================

testEmptyDB :: Assertion
testEmptyDB =
  emptyConstructorDB @?= Map.empty

testAddConstructor :: Assertion
testAddConstructor =
  let db = emptyConstructorDB
      listTy = qname [] "List"
      nilCtor = Constructor
        { ctorName = qname [] "Nil"
        , ctorArity = 0
        , ctorArgTypes = []
        , ctorReturnType = typeConApp [] "List" [TyVar (var "a" 0)]
        }
      db' = addConstructor db listTy nilCtor
  in Map.size db' @?= 1

testQueryConstructors :: Assertion
testQueryConstructors =
  let db = emptyConstructorDB
      listTy = qname [] "List"
      nilCtor = Constructor
        { ctorName = qname [] "Nil"
        , ctorArity = 0
        , ctorArgTypes = []
        , ctorReturnType = typeConApp [] "List" [TyVar (var "a" 0)]
        }
      db' = addConstructor db listTy nilCtor
  in case queryConstructors db' listTy of
       Right ctors -> length ctors @?= 1
       Left _ -> assertFailure "Query failed"

testQueryUnknownType :: Assertion
testQueryUnknownType =
  let db = emptyConstructorDB
      unknownTy = qname [] "Unknown"
  in case queryConstructors db unknownTy of
       Right _ -> assertFailure "Should fail for unknown type"
       Left (UnknownConstructor _) -> return ()
       Left err -> assertFailure $ "Wrong error: " ++ show err

-- ============================================================================
-- PATTERN COMPILATION TESTS
-- ============================================================================

testCompileVarPattern :: Assertion
testCompileVarPattern =
  let pat = PatVar (var "x" 0)
  in case compilePattern pat of
       Right cp -> do
         cpOriginal cp @?= pat
         Map.size (cpBindings cp) @?= 1
       Left err -> assertFailure $ "Compilation failed: " ++ prettyPatternError err

testCompileWildcardPattern :: Assertion
testCompileWildcardPattern =
  let pat = PatWildcard
  in case compilePattern pat of
       Right cp -> do
         cpOriginal cp @?= pat
         Map.size (cpBindings cp) @?= 0
       Left err -> assertFailure $ "Compilation failed: " ++ prettyPatternError err

testCompileConstrPattern :: Assertion
testCompileConstrPattern =
  let pat = PatConstr (qname [] "Cons") [PatVar (var "h" 0), PatVar (var "t" 1)]
  in case compilePattern pat of
       Right cp -> do
         cpOriginal cp @?= pat
         Map.size (cpBindings cp) @?= 2
       Left err -> assertFailure $ "Compilation failed: " ++ prettyPatternError err

testCompileNestedPatterns :: Assertion
testCompileNestedPatterns =
  let pat = PatConstr (qname [] "Cons")
              [PatVar (var "h" 0), PatConstr (qname [] "Cons") [PatVar (var "h2" 1), PatWildcard]]
  in case compilePattern pat of
       Right cp -> do
         cpOriginal cp @?= pat
         Map.size (cpBindings cp) @?= 2
       Left err -> assertFailure $ "Compilation failed: " ++ prettyPatternError err

testExtractBindings :: Assertion
testExtractBindings =
  let pat = PatConstr (qname [] "Cons") [PatVar (var "h" 0), PatVar (var "t" 1)]
      ty = typeConApp [] "List" [typeCon [] "Int"]
      bindings = extractPatternBindings pat ty
  in Map.size bindings @?= 2

-- ============================================================================
-- EXHAUSTIVENESS CHECKING TESTS
-- ============================================================================

testDefaultExhaustive :: Assertion
testDefaultExhaustive =
  let db = emptyConstructorDB
      ty = typeCon [] "Bool"
      patterns = [PatConstr (qname [] "True") []]
      defClause = Just PatWildcard
  in case checkExhaustive db ty patterns defClause of
       Right () -> return ()
       Left err -> assertFailure $ "Should be exhaustive: " ++ prettyExhaustiveError err

testEmptyPatternsWithDefault :: Assertion
testEmptyPatternsWithDefault =
  let db = emptyConstructorDB
      ty = typeCon [] "Any"
      patterns = []
      defClause = Just PatWildcard
  in case checkExhaustive db ty patterns defClause of
       Right () -> return ()
       Left err -> assertFailure $ "Should be exhaustive: " ++ prettyExhaustiveError err

testWildcardExhaustive :: Assertion
testWildcardExhaustive =
  let db = emptyConstructorDB
      ty = typeCon [] "Any"
      patterns = [PatWildcard]
      defClause = Nothing
  in case checkExhaustive db ty patterns defClause of
       Right () -> return ()
       Left err -> assertFailure $ "Should be exhaustive: " ++ prettyExhaustiveError err

-- ============================================================================
-- FIRST MILESTONE: LIST PATTERN MATCHING
-- ============================================================================

{-| Set up a constructor database with List type containing Nil and Cons.
-}
mkListDB :: ConstructorDB
mkListDB =
  let db = emptyConstructorDB
      listTy = qname [] "List"

      -- Nil : List a
      nilCtor = Constructor
        { ctorName = qname [] "Nil"
        , ctorArity = 0
        , ctorArgTypes = []
        , ctorReturnType = typeConApp [] "List" [TyVar (var "a" 0)]
        }

      -- Cons : a -> List a -> List a
      consCtor = Constructor
        { ctorName = qname [] "Cons"
        , ctorArity = 2
        , ctorArgTypes = [TyVar (var "a" 0), typeConApp [] "List" [TyVar (var "a" 0)]]
        , ctorReturnType = typeConApp [] "List" [TyVar (var "a" 0)]
        }

      db' = addConstructor db listTy nilCtor
      db'' = addConstructor db' listTy consCtor
  in db''

testListTypeDB :: Assertion
testListTypeDB =
  let db = mkListDB
      listTy = qname [] "List"
  in case queryConstructors db listTy of
       Right ctors -> do
         length ctors @?= 2
         [ctorArity c | c <- ctors] `shouldContain` [0, 2]
       Left err -> assertFailure $ "Query failed: " ++ prettyPatternError err
  where
    shouldContain xs ys = all (\y -> elem y xs) ys @?= True

testSimpleListPatterns :: Assertion
testSimpleListPatterns =
  let nilPat = PatConstr (qname [] "Nil") []
      consPat = PatConstr (qname [] "Cons") [PatVar (var "h" 0), PatVar (var "t" 1)]
  in do
    Right cp1 <- return $ compilePattern nilPat
    Right cp2 <- return $ compilePattern consPat

    cpOriginal cp1 @?= nilPat
    cpOriginal cp2 @?= consPat

    Map.size (cpBindings cp1) @?= 0
    Map.size (cpBindings cp2) @?= 2

testListPatternsExhaustive :: Assertion
testListPatternsExhaustive =
  let db = mkListDB
      listTy = typeConApp [] "List" [typeCon [] "Int"]
      nilPat = PatConstr (qname [] "Nil") []
      consPat = PatConstr (qname [] "Cons") [PatVar (var "h" 0), PatVar (var "t" 1)]
      patterns = [nilPat, consPat]
  in case checkExhaustive db listTy patterns Nothing of
       Right () -> return ()
       Left err -> assertFailure $ "Should be exhaustive: " ++ prettyExhaustiveError err

-- ============================================================================
-- PATTERN TYPE INFERENCE TESTS
-- ============================================================================

testInferVarType :: Assertion
testInferVarType =
  let ctx = PatternContext
        { pcType = typeCon [] "Int"
        , pcConstructors = emptyConstructorDB
        }
      pat = PatVar (var "x" 0)
  in case inferPatternType ctx pat of
       Right ty -> ty @?= typeCon [] "Int"
       Left err -> assertFailure $ "Type inference failed: " ++ prettyPatternError err

testInferConstrType :: Assertion
testInferConstrType =
  let ctx = PatternContext
        { pcType = typeConApp [] "List" [typeCon [] "Bool"]
        , pcConstructors = mkListDB
        }
      pat = PatConstr (qname [] "Nil") []
  in case inferPatternType ctx pat of
       Right ty -> ty @?= typeConApp [] "List" [typeCon [] "Bool"]
       Left err -> assertFailure $ "Type inference failed: " ++ prettyPatternError err

-- ============================================================================
-- ERROR HANDLING TESTS
-- ============================================================================

testUnknownConstructorError :: Assertion
testUnknownConstructorError =
  let db = emptyConstructorDB
      unknownTy = qname [] "Unknown"
  in case queryConstructors db unknownTy of
       Left (UnknownConstructor _) -> return ()
       Right _ -> assertFailure "Should fail"
       Left err -> assertFailure $ "Wrong error type: " ++ show err

testArityMismatchError :: Assertion
testArityMismatchError =
  let arity = 3
      expected = 2
  in case Left (ConstructorArityMismatch (qname [] "Cons") expected arity) of
       Left (ConstructorArityMismatch _ exp act) -> do
         exp @?= expected
         act @?= arity
       _ -> assertFailure "Error construction failed"

-- ============================================================================
-- PATTERN UTILITY TESTS
-- ============================================================================

testIsWildcardPattern :: Assertion
testIsWildcardPattern = do
  isWildcardPattern PatWildcard @?= True
  isWildcardPattern (PatVar (var "x" 0)) @?= True
  isWildcardPattern (PatConstr (qname [] "Nil") []) @?= False

testPatternConstructors :: Assertion
testPatternConstructors =
  let pat = PatConstr (qname [] "Cons")
              [PatVar (var "h" 0), PatConstr (qname [] "Nil") []]
      ctors = patternConstructors pat
  in do
    Set.member (qname [] "Cons") ctors @?= True
    Set.member (qname [] "Nil") ctors @?= True
    Set.size ctors @?= 2

testRedundantPatterns :: Assertion
testRedundantPatterns =
  let nilPat = PatConstr (qname [] "Nil") []
      consPat = PatConstr (qname [] "Cons") [PatVar (var "h" 0), PatVar (var "t" 1)]
      dupNil = PatConstr (qname [] "Nil") []
  in do
    isRedundant [nilPat] dupNil @?= True
    isRedundant [nilPat] consPat @?= False
    isRedundant [] nilPat @?= False