File size: 15,625 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 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 | {-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE DeriveTraversable #-}
{-|
Module : Assertica.Core.Pattern
Description : Pattern matching compilation and exhaustiveness checking
Copyright : (c) 2026 Ahmad Ali Parr
License : BSL-1.0
This module implements pattern matching compilation: transforming surface pattern
syntax into core eliminators that preserve type information and exhaustiveness
guarantees.
DESIGN PRINCIPLES:
1. **Exhaustiveness checking**: All cases must be covered or default provided.
No unmatched patterns reach the kernel.
2. **Type preservation**: Pattern compilation preserves types through pattern variables.
3. **Constructor awareness**: Pattern compiler knows constructor arities and argument types.
4. **Dependent pattern matching**: Support dependent patterns where types depend on matched values.
5. **Fail-closed**: Ill-formed patterns rejected with clear diagnostics.
ARCHITECTURE:
The pattern compiler sits between elaboration and type checking:
Elaborator (Agent 3A)
↓ produces SurfacePattern
Parser/Elaboration
↓
Pattern Compiler (THIS MODULE)
↓ validates + compiles to Case
↓
Type Checker (Agent 3B)
↓
Kernel Verification
REQUIRED INTEGRATION POINTS:
- QueryConstructors: Look up constructor arities from type environment
- CompiledPattern: Pattern variables must have inferred types
- TypeChecker receives compiled Case expressions with type preservation
-}
module Assertica.Core.Pattern
( -- * Pattern AST (extended)
Pattern(..)
, PatternVar
, PatternInfo(..)
-- * Constructor information
, Constructor(..)
, ConstructorDB
, emptyConstructorDB
, addConstructor
, queryConstructors
, queryConstructorArity
-- * Exhaustiveness checking
, ExhaustiveResult(..)
, checkExhaustive
, findMissingCases
, isRedundant
-- * Pattern compilation
, compilePattern
, compilePatterns
, CompiledPattern(..)
, PatternBindings
, extractPatternBindings
-- * Type-aware pattern matching
, PatternContext(..)
, inferPatternType
, checkPatternType
-- * Error types and diagnostics
, PatternError(..)
, prettyPatternError
, ExhaustiveError(..)
, prettyExhaustiveError
) where
import qualified Data.Set as Set
import qualified Data.Map.Strict as Map
import Data.Text (Text)
import qualified Data.Text as T
import Data.List (intercalate, nub)
import Data.Maybe (catMaybes, isJust, fromMaybe)
import GHC.Generics (Generic)
import Data.Typeable (Typeable)
import Assertica.Core.AST
( Var(..)
, QName(..)
, Type(..)
, Term(..)
, Pattern(..)
, Clause(..)
, Binder(..)
, varName
, prettyPattern
, prettyType
)
-- ============================================================================
-- PATTERN AST (EXTENDED)
-- ============================================================================
{-| Information about a pattern variable binding.
Tracks the variable name and its inferred or declared type.
-}
data PatternVar = PatternVar
{ pvVar :: Var -- ^ The bound variable
, pvType :: Maybe Type -- ^ Inferred or declared type
}
deriving (Eq, Show, Generic, Typeable)
{-| Extended pattern information for compilation.
-}
data PatternInfo = PatternInfo
{ piPattern :: Pattern -- ^ The pattern itself
, piBindings :: PatternBindings -- ^ Variables bound by this pattern
, piType :: Maybe Type -- ^ Expected or inferred type
}
deriving (Eq, Show, Generic, Typeable)
-- ============================================================================
-- CONSTRUCTOR DATABASE
-- ============================================================================
{-| Information about a data constructor.
-}
data Constructor = Constructor
{ ctorName :: QName -- ^ Qualified name of constructor
, ctorArity :: Int -- ^ Number of arguments
, ctorArgTypes :: [Type] -- ^ Types of arguments (may be dependent)
, ctorReturnType :: Type -- ^ Return type of constructor
}
deriving (Eq, Show, Generic, Typeable)
{-| Database of known constructors.
Maps type names to their constructors.
-}
type ConstructorDB = Map.Map QName [Constructor]
{-| Create an empty constructor database.
-}
emptyConstructorDB :: ConstructorDB
emptyConstructorDB = Map.empty
{-| Add a constructor to the database.
-}
addConstructor :: ConstructorDB -> QName -> Constructor -> ConstructorDB
addConstructor db tyName ctor =
Map.insertWith (++) tyName [ctor] db
{-| Query all constructors for a given type.
-}
queryConstructors :: ConstructorDB -> QName -> Either PatternError [Constructor]
queryConstructors db tyName =
case Map.lookup tyName db of
Just ctors -> Right ctors
Nothing -> Left (UnknownConstructor tyName)
{-| Query the arity of a specific constructor.
-}
queryConstructorArity :: ConstructorDB -> QName -> Either PatternError Int
queryConstructorArity db ctorName = do
-- Search through all constructors in all types
let allCtors = concat (Map.elems db)
case filter (\c -> ctorName == ctorName) allCtors of
(ctor:_) -> Right (ctorArity ctor)
[] -> Left (UnknownConstructor ctorName)
-- ============================================================================
-- EXHAUSTIVENESS CHECKING
-- ============================================================================
{-| Result of exhaustiveness checking.
-}
data ExhaustiveResult
= Exhaustive -- ^ All cases covered
| MissingCases [Pattern] -- ^ Missing constructor patterns
| HasDefault -- ^ Has a default clause (always safe)
deriving (Eq, Show, Generic, Typeable)
{-| Error type for exhaustiveness checking failures.
-}
data ExhaustiveError
= IncompleteMatch [Pattern] -- ^ Patterns that are not covered
| DuplicatePattern Pattern -- ^ Duplicate or redundant pattern
| UnreachablePattern Pattern -- ^ Pattern can never match
deriving (Eq, Show, Generic, Typeable)
{-| Pretty-print an exhaustive checking error.
-}
prettyExhaustiveError :: ExhaustiveError -> String
prettyExhaustiveError = \case
IncompleteMatch pats ->
"Incomplete pattern match. Missing cases:\n " ++
intercalate "\n " (map prettyPattern pats)
DuplicatePattern pat ->
"Duplicate or redundant pattern: " ++ prettyPattern pat
UnreachablePattern pat ->
"Unreachable pattern: " ++ prettyPattern pat
{-| Check if a set of patterns exhaustively covers a type.
Returns:
- Right () if patterns are exhaustive or have a default
- Left (IncompleteMatch missing) if patterns don't cover all cases
- Left (DuplicatePattern pat) if there are redundant patterns
For this implementation, we need the constructor database to determine
which constructors must be matched.
-}
checkExhaustive :: ConstructorDB -> Type -> [Pattern] -> Maybe Pattern
-> Either ExhaustiveError ()
checkExhaustive _db _ty _patterns (Just _) =
-- If there's a default clause, patterns are always exhaustive
Right ()
checkExhaustive db ty patterns Nothing = do
-- Check for duplicates
let patStrs = map prettyPattern patterns
if length (nub patStrs) /= length patStrs
then Left (DuplicatePattern (head patterns))
else Right ()
-- Find missing cases
missing <- findMissingCases db ty patterns
if null missing
then Right ()
else Left (IncompleteMatch missing)
{-| Find pattern cases that are not covered by the given patterns.
-}
findMissingCases :: ConstructorDB -> Type -> [Pattern]
-> Either ExhaustiveError [Pattern]
findMissingCases db ty patterns = do
-- Get the type name
let tyName = case ty of
TyConst name _ -> name
_ -> QName [] "Unknown"
-- Query available constructors
ctors <- queryConstructors db tyName
-- Extract constructor names from patterns
let coveredCtors = Set.fromList
[name | PatConstr name _ <- patterns]
-- Find uncovered constructors
let uncovered = [ctor | ctor <- ctors, not (Set.member (ctorName ctor) coveredCtors)]
-- Create missing patterns (wildcards for now)
let missingPats = [PatConstr (ctorName c) (replicate (ctorArity c) PatWildcard) | c <- uncovered]
Right missingPats
{-| Check if a pattern is redundant (unreachable).
A pattern is redundant if it cannot match any value that wasn't already
matched by earlier patterns.
This is a simplified check: we mark a pattern as potentially redundant if
it matches a constructor that was already matched.
-}
isRedundant :: [Pattern] -> Pattern -> Bool
isRedundant earlier pat =
case pat of
PatConstr name _ ->
any (\case PatConstr n _ -> n == name; _ -> False) earlier
PatWildcard -> any (== PatWildcard) earlier
PatVar _ -> False -- Variables always bind, never redundant
PatLit _ -> False -- Literal patterns handled separately (not yet implemented)
-- ============================================================================
-- PATTERN COMPILATION
-- ============================================================================
{-| Type for pattern bindings (variable -> type mappings).
-}
type PatternBindings = Map.Map Var Type
{-| Compiled pattern information.
-}
data CompiledPattern = CompiledPattern
{ cpOriginal :: Pattern -- ^ Original surface pattern
, cpBindings :: PatternBindings -- ^ Variables bound by this pattern
, cpType :: Maybe Type -- ^ Inferred type for this pattern
}
deriving (Eq, Show, Generic, Typeable)
{-| Extract all pattern variable bindings from a pattern.
This walks the pattern tree and collects all bound variables with their types.
-}
extractPatternBindings :: Pattern -> Type -> PatternBindings
extractPatternBindings pat ty = go pat ty
where
go :: Pattern -> Type -> PatternBindings
go p t = case p of
PatVar v -> Map.singleton v t
PatConstr _ subPats ->
-- For constructor patterns, extract bindings from subpatterns
-- This is simplified: in full implementation, we'd use constructor arg types
let subTypes = replicate (length subPats) t
in Map.unions [go subpat subty | (subpat, subty) <- zip subPats subTypes]
PatWildcard -> Map.empty
PatLit _ -> Map.empty
{-| Compile a single pattern to a core pattern.
This validates the pattern structure and preserves type information.
-}
compilePattern :: Pattern -> Either PatternError CompiledPattern
compilePattern pat = do
-- For now, minimal compilation: just validate structure
case pat of
PatVar v ->
Right (CompiledPattern pat (Map.singleton v (TyVar v)) Nothing)
PatConstr name subPats -> do
-- Validate that we can compile subpatterns
compiledSubs <- mapM compilePattern subPats
let allBindings = Map.unions [cpBindings cp | cp <- compiledSubs]
Right (CompiledPattern pat allBindings Nothing)
PatWildcard ->
Right (CompiledPattern pat Map.empty Nothing)
PatLit _ ->
Right (CompiledPattern pat Map.empty Nothing)
{-| Compile a list of patterns to core patterns.
Returns a list of compiled patterns with bindings and type information.
-}
compilePatterns :: [Pattern] -> Either PatternError [CompiledPattern]
compilePatterns = mapM compilePattern
-- ============================================================================
-- PATTERN TYPE INFERENCE
-- ============================================================================
{-| Context for type-aware pattern matching.
-}
data PatternContext = PatternContext
{ pcType :: Type -- ^ Expected type for patterns
, pcConstructors :: ConstructorDB -- ^ Available constructors
}
deriving (Eq, Show, Generic, Typeable)
{-| Infer the type of a pattern variable.
Given a pattern and an expected type, infer the type of any bound variables.
-}
inferPatternType :: PatternContext -> Pattern -> Either PatternError Type
inferPatternType ctx = \case
PatVar _ -> Right (pcType ctx)
PatConstr _ _ -> Right (pcType ctx)
PatWildcard -> Right (pcType ctx)
PatLit _ -> Right (pcType ctx)
{-| Check that a pattern matches an expected type.
Returns the refined type for the pattern (in case of dependent pattern matching).
-}
checkPatternType :: PatternContext -> Pattern -> Type
-> Either PatternError Type
checkPatternType _ctx _pat expectedTy =
-- For now, just accept the expected type
Right expectedTy
-- ============================================================================
-- ERROR TYPES
-- ============================================================================
{-| Pattern compilation and checking errors.
-}
data PatternError
= UnknownConstructor QName
| ConstructorArityMismatch QName Int Int -- ^ (constructor, expected, actual)
| DuplicateConstructor QName
| InvalidPattern String
| PatternTypeError String
| UncoveredConstructor QName
deriving (Eq, Show, Generic, Typeable)
{-| Pretty-print a pattern error.
-}
prettyPatternError :: PatternError -> String
prettyPatternError = \case
UnknownConstructor (QName mod name) ->
"Unknown constructor: " ++ qualName mod name
ConstructorArityMismatch (QName mod name) expected actual ->
qualName mod name ++ " expects " ++ show expected ++ " arguments, got " ++ show actual
DuplicateConstructor (QName mod name) ->
"Duplicate constructor: " ++ qualName mod name
InvalidPattern msg ->
"Invalid pattern: " ++ msg
PatternTypeError msg ->
"Pattern type error: " ++ msg
UncoveredConstructor (QName mod name) ->
"Pattern match is not exhaustive for constructor: " ++ qualName mod name
where
qualName mod name =
if null mod
then T.unpack name
else intercalate "." (map T.unpack mod) ++ "." ++ T.unpack name
-- ============================================================================
-- INTEGRATION: PATTERN COMPILATION TO CORE CASE
-- ============================================================================
{-| Compile patterns and clauses to a core Case expression.
This is the main entry point for the pattern compiler. It takes a list of
surface patterns and produces a typed core Case expression.
Preconditions:
- Patterns are exhaustive (or a default is provided)
- Constructors are known in the database
- Type information is available
Postconditions:
- Compiled patterns preserve type information
- Pattern variables are properly bound
- Case expression is ready for type checking
-}
compilePatternCases :: ConstructorDB -> Term -> [Clause]
-> Either PatternError Term
compilePatternCases _db scrutinee clauses = do
-- Compile each clause
_compiledClauses <- mapM compileSingleClause clauses
-- For now, return the case expression as-is
-- The actual transformation would happen here
Right (Case scrutinee clauses Nothing)
where
compileSingleClause (Clause pat body) = do
cp <- compilePattern pat
let bindings = cpBindings cp
-- The body should have all variables from bindings in scope
return (Clause pat body, bindings)
-- ============================================================================
-- UTILITIES
-- ============================================================================
{-| Check if a pattern is wildcard-like (matches anything).
-}
isWildcardPattern :: Pattern -> Bool
isWildcardPattern = \case
PatWildcard -> True
PatVar _ -> True
_ -> False
{-| Extract all constructors mentioned in a pattern.
-}
patternConstructors :: Pattern -> Set.Set QName
patternConstructors = \case
PatConstr name subPats ->
Set.insert name (Set.unions (map patternConstructors subPats))
PatVar _ -> Set.empty
PatWildcard -> Set.empty
PatLit _ -> Set.empty
|