File size: 11,749 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
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveShow #-}
{-# LANGUAGE OverloadedStrings #-}

{-|
Module      : Assertica.Core.Positivity
Description : Positivity checking for inductive type definitions
Copyright   : (c) 2026 Ahmad Ali Parr
License     : BSL-1.0

This module implements positivity checking to ensure inductive type definitions
are strictly positive. A type parameter is strictly positive if it appears only
in positive positions (as data constructor arguments), never in negative positions
(such as function domain types).

DEFINITION: Strict Positivity
- A type variable 'a' is POSITIVE in a type if it appears only in positions where
  it could not lead to inconsistency (e.g., as constructor argument)
- A type variable 'a' is NEGATIVE if it appears in a function domain (a -> T)
- A type is STRICTLY POSITIVE if all type parameters appear only in positive positions

EXAMPLES:
- `data List a = Nil | Cons a (List a)` is strictly positive
  - 'a' appears as constructor argument (positive position)
  - 'List a' appears as constructor argument (positive position)

- `data Tree a = Leaf a | Node (Tree a) (Tree a)` is strictly positive
  - 'a' appears as constructor argument
  - Recursive occurrences are in positive positions

- `data BadType a = Bad (a -> Int)` is NOT strictly positive
  - 'a' appears in function domain (negative position)

- `data BadList a = Cons a (BadList (a -> Int))` is NOT strictly positive
  - 'a' appears in negative position via function type argument

DESIGN PRINCIPLES:
1. **Fail-closed**: Position violations are always rejected
2. **Occurs-check**: Track all occurrences of type parameters
3. **Recursive tracking**: Handle nested type structures
4. **Clear diagnostics**: Report exactly which position violates positivity
5. **Integration**: Works with type definitions from the AST

ALGORITHM:
1. For each type definition, extract type parameters
2. For each constructor, analyze argument types
3. Track position context (positive/negative)
4. When entering function domain, flip position
5. When entering constructor argument, maintain/flip based on nesting
6. Report violations with location information
-}

module Assertica.Core.Positivity
  ( -- * Main positivity checking
    checkPositivity
  , checkPositivityMulti
  , needsPositivityCheck

    -- * Error type
  , PosError(..)
  , prettyPosError

    -- * Analysis utilities (for testing)
  , analyzeTypeOccurrences
  , TypeOccurrence(..)
  , Position(..)
  ) where

import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Set as Set
import Data.Set (Set)
import qualified Data.Map.Strict as Map
import Data.Map.Strict (Map)
import Data.List (intercalate)
import GHC.Generics (Generic)

import Assertica.Core.AST
import Assertica.Core.TypeEnv

-- ============================================================================
-- ERROR TYPES
-- ============================================================================

{-| Positivity checking errors. -}
data PosError
  = NonPositiveOccurrence Text String
    -- ^ Type parameter appears in negative position with explanation
  | TypeVarInFunctionDomain Text
    -- ^ Type variable appears in function argument type
  | NestedNegativeOccurrence Text String
    -- ^ Type parameter nested in negative position
  | ImpredicativeNesting String
    -- ^ Impredicative nesting detected
  deriving (Eq, Show, Generic)

{-| Pretty-print a positivity error for user display. -}
prettyPosError :: PosError -> String
prettyPosError = \case
  NonPositiveOccurrence param msg ->
    "Type parameter '" ++ T.unpack param ++
    "' appears in non-positive position: " ++ msg
  TypeVarInFunctionDomain param ->
    "Type parameter '" ++ T.unpack param ++
    "' appears in function domain (negative position)"
  NestedNegativeOccurrence param msg ->
    "Type parameter '" ++ T.unpack param ++
    "' nested in negative position: " ++ msg
  ImpredicativeNesting msg ->
    "Impredicative nesting prevents strict positivity: " ++ msg

-- ============================================================================
-- POSITION TRACKING
-- ============================================================================

{-| Polarity/position in type structure. -}
data Position
  = Positive      -- ^ Positive position (e.g., constructor argument)
  | Negative      -- ^ Negative position (e.g., function domain)
  deriving (Eq, Show, Generic)

{-| Flip polarity when entering negative context. -}
flipPos :: Position -> Position
flipPos Positive = Negative
flipPos Negative = Positive

{-| A type occurrence in a type definition. -}
data TypeOccurrence = TypeOccurrence
  { occVar      :: Text       -- ^ The type variable
  , occPosition :: Position   -- ^ Where it appears
  , occContext  :: String     -- ^ Contextual explanation
  }
  deriving (Eq, Show, Generic)

-- ============================================================================
-- POSITIVITY CHECKING INTERFACE
-- ============================================================================

{-| Check if a type definition needs positivity checking.
Only inductive type definitions (data types) need checking.
-}
needsPositivityCheck :: Type -> Bool
needsPositivityCheck = \case
  TyConst _ _ -> True   -- Constructor type definition
  TyForall _ t -> needsPositivityCheck t
  _ -> False

{-| Check positivity of a single type definition.

Verifies that all type parameters appear only in strictly positive positions.

Returns:
- Left PosError if positivity is violated
- Right () if the type is strictly positive
-}
checkPositivity :: Text -> [Var] -> [Type] -> Either PosError ()
checkPositivity _typeName params constructorTypes = do
  let paramNames = Set.fromList [varName p | p <- params]
  mapM_ (\cty -> checkConstructorType paramNames cty) constructorTypes

{-| Check positivity of a constructor type argument. -}
checkConstructorType :: Set Text -> Type -> Either PosError ()
checkConstructorType paramNames ty =
  case analyzeTypeOccurrences paramNames ty of
    [] -> Right ()
    occs -> do
      let negativeOccs = [o | o <- occs, occPosition o == Negative]
      case negativeOccs of
        [] -> Right ()
        (occ:_) ->
          Left $ NonPositiveOccurrence
            (occVar occ)
            ("in " ++ occContext occ)

{-| Check positivity for multiple type definitions (e.g., mutual inductive types). -}
checkPositivityMulti :: [(Text, [Var], [Type])] -> Either PosError ()
checkPositivityMulti defs =
  mapM_ (\(name, params, ctors) -> checkPositivity name params ctors) defs

-- ============================================================================
-- OCCURRENCE ANALYSIS
-- ============================================================================

{-| Analyze all occurrences of type parameters in a type. -}
analyzeTypeOccurrences :: Set Text -> Type -> [TypeOccurrence]
analyzeTypeOccurrences params ty =
  analyzeType params ty Positive

{-| Recursively analyze a type, tracking position. -}
analyzeType :: Set Text -> Type -> Position -> [TypeOccurrence]
analyzeType params ty pos = go ty
  where
    go = \case
      TyVar (Var name _) ->
        if Set.member name params
          then [TypeOccurrence name pos (describePosition pos)]
          else []

      TyConst _qname args ->
        -- Type constructor arguments are positive
        concatMap (\arg -> analyzeType params arg Positive) args

      TyFun domain codomain ->
        -- Function domain is negative position (flip polarity)
        -- Function codomain is positive
        let domain_occs = analyzeType params domain (flipPos pos)
            codomain_occs = analyzeType params codomain pos
        in domain_occs ++ codomain_occs

      TyForall (Binder (Var bvar _) mbty) rest ->
        -- Skip analysis of bound variable
        let bty_occs = case mbty of
              Nothing -> []
              Just bty -> analyzeType params bty Positive
            rest_occs = analyzeType params rest pos
        in bty_occs ++ rest_occs

      TyUniverse _ -> []

      TyApp func arg ->
        -- Application: argument is typically positive
        analyzeType params func pos ++
        analyzeType params arg Positive

      TyEq e1 e2 ->
        -- Equality type contains terms, not type parameters
        -- Type parameters in terms are problematic
        analyzeTypeInTerms params e1 ++
        analyzeTypeInTerms params e2

{-| Analyze type parameters appearing in terms (rare, problematic). -}
analyzeTypeInTerms :: Set Text -> Term -> [TypeOccurrence]
analyzeTypeInTerms params term =
  case findTypeVarsInTerm params term of
    [] -> []
    vars -> [TypeOccurrence v Negative "in term equality" | v <- vars]

{-| Find type variable names in a term (as variables).
This is a heuristic - in a real type system, type parameters shouldn't appear in terms.
-}
findTypeVarsInTerm :: Set Text -> Term -> [Text]
findTypeVarsInTerm params = go
  where
    go = \case
      Var (Var name _) ->
        if Set.member name params then [name] else []
      Const _ -> []
      Lam _binder body -> go body
      App f x -> go f ++ go x
      Constr _qname args -> concatMap go args
      Case scrutinee clauses def ->
        go scrutinee ++
        concatMap (\(Clause _pat body) -> go body) clauses ++
        maybe [] go def
      Let _binder e1 e2 -> go e1 ++ go e2
      Ann e _ty -> go e
      Forall _binder body -> go body
      ProofTerm _p -> []
      Prop _prop -> []

{-| Describe the position in a human-readable way. -}
describePosition :: Position -> String
describePosition = \case
  Positive -> "positive position (constructor argument)"
  Negative -> "negative position (function domain)"

-- ============================================================================
-- STRICT POSITIVITY CHECKING (advanced)
-- ============================================================================

{-| Check strict positivity: type parameter appears only in positive positions
within constructor arguments.

This is more restrictive than just positivity - it prevents recursive definitions
that could be inconsistent.
-}
checkStrictPositivity :: Set Text -> Type -> Either PosError ()
checkStrictPositivity params ty = do
  let occs = analyzeTypeOccurrences params ty
  case [o | o <- occs, occPosition o == Negative] of
    [] -> Right ()
    (neg:_) ->
      Left $ TypeVarInFunctionDomain (occVar neg)

-- ============================================================================
-- UTILITIES
-- ============================================================================

{-| Check if a type parameter occurs in a type (occurs check). -}
typeParamOccurs :: Text -> Type -> Bool
typeParamOccurs param ty = go ty
  where
    go = \case
      TyVar (Var name _) -> name == param
      TyConst _qname args -> any go args
      TyFun domain codomain -> go domain || go codomain
      TyForall (Binder (Var _bvar _) mbty) rest ->
        case mbty of
          Nothing -> go rest
          Just bty -> go bty || go rest
      TyUniverse _ -> False
      TyApp func arg -> go func || go arg
      TyEq e1 e2 -> paramInTerm param e1 || paramInTerm param e2

{-| Check if a type parameter (viewed as a variable) occurs in a term. -}
paramInTerm :: Text -> Term -> Bool
paramInTerm param = go
  where
    go = \case
      Var (Var name _) -> name == param
      Const _ -> False
      Lam _binder body -> go body
      App f x -> go f || go x
      Constr _qname args -> any go args
      Case scrutinee clauses def ->
        go scrutinee ||
        any (\(Clause _pat body) -> go body) clauses ||
        maybe False go def
      Let _binder e1 e2 -> go e1 || go e2
      Ann e _ty -> go e
      Forall _binder body -> go body
      ProofTerm _p -> False
      Prop _prop -> False