packages feed

recover-rtti-0.5.1: tests/Test/RecoverRTTI/ConcreteClassifier.hs

module Test.RecoverRTTI.ConcreteClassifier (
    -- * Concrete classifier
    ConcreteClassifier
  , ClassifyUser(..)
    -- * Values
  , Value(..)
    -- * Constraints
  , canShowConcrete
  , canCompareConcrete
    -- * Size
  , sizeUser
  , sizeConcrete
    -- * Same classifier
  , sameUser
  , sameConcrete
    -- * Equality
    -- * Arbitrary
  , arbitraryUser
  , arbitraryConcrete
  ) where

import Data.Kind
import Data.SOP
import Data.SOP.Dict
import Data.Type.Equality
import Data.Void

import Debug.RecoverRTTI
import Debug.RecoverRTTI.Classify

import Test.QuickCheck (Arbitrary(..), Gen)
import Test.QuickCheck qualified as QC

import Test.RecoverRTTI.Classifier.Arbitrary
import Test.RecoverRTTI.Classifier.Equality ()
import Test.RecoverRTTI.Classifier.Size
import Test.RecoverRTTI.QuickCheck.DepGen
import Test.RecoverRTTI.QuickCheck.Sized (SizedGen)
import Test.RecoverRTTI.QuickCheck.Sized qualified as SG
import Test.RecoverRTTI.UserDefined

{-------------------------------------------------------------------------------
  Concrete classifier

  The difference between the " concrete " classifier and the 'Classifier' from
  the main library is that the former has explicit cases for user-defined types,
  and the latter doesn't (merely classifying them as 'UserDefined').

  In "Test.RecoverRRTI.Staged" we show that we can do staged inference,
  using 'classify' repeatedly to recover /all/ (concrete) type information
  from the type information returned by 'classify' (/if/ we have full
  information about which user-defined types we're interested in).
-------------------------------------------------------------------------------}

type ConcreteClassifier = Classifier_ ClassifyUser

data ClassifyUser (a :: Type) where
  C_Simple   :: ClassifyUser SimpleType
  C_NonRec   :: Elems ClassifyUser '[a] -> ClassifyUser (NonRecursive a)
  C_Rec      :: Elems ClassifyUser '[a] -> ClassifyUser (Recursive    a)
  C_Unlifted :: ClassifyUser ContainsUnlifted

deriving instance Show (ClassifyUser a)

{-------------------------------------------------------------------------------
  Values
-------------------------------------------------------------------------------}

-- | Like 'Classified', but using 'ConcreteClassifier'
--
-- For convenience, we also include some constraints here, even though they
-- are in fact derivable from the classifier
data Value a where
   Value :: (Show a, Eq a) => ConcreteClassifier a -> a -> Value a

deriving instance Show (Value a)
deriving instance Show (Some Value)

instance Arbitrary (Some Value) where
  arbitrary = do
      -- We don't want to generate large classifiers
      Some (DepGen cc gen) <- SG.run 10 arbitraryConcrete

      -- For the values however we want to be able to generate larger trees
      Some . Value cc <$> SG.run 1000 gen

{-------------------------------------------------------------------------------
  Constraints
-------------------------------------------------------------------------------}

class (
    c SimpleType
  , forall a. c a => c (NonRecursive a)
  , forall a. c a => c (Recursive    a)
  , c ContainsUnlifted
  ) => UserSatisfies c

instance (
    c SimpleType
  , forall a. c a => c (NonRecursive a)
  , forall a. c a => c (Recursive    a)
  , c ContainsUnlifted
  ) => UserSatisfies c

userSatisfies :: forall c.
     (ClassifiedSatisfies c, c Void, UserSatisfies c)
  => (forall a. ClassifyUser a -> Dict c a)
userSatisfies = go
  where
    go :: ClassifyUser a -> Dict c a
    go  C_Simple    = Dict
    go (C_NonRec c) = goElems c $ Dict
    go (C_Rec    c) = goElems c $ Dict
    go  C_Unlifted  = Dict

    goElems :: SListI as => Elems ClassifyUser as -> (All c as => r) -> r
    goElems (Elems cs) k = case all_NP (hmap goElem cs) of Dict -> k

    goElem :: Elem ClassifyUser a -> Dict c a
    goElem (Elem c) = concreteSatisfies c
    goElem NoElem   = Dict

concreteSatisfies ::
     (ClassifiedSatisfies c, c Void, UserSatisfies c)
  => ConcreteClassifier a -> Dict c a
concreteSatisfies = classifiedSatisfies userSatisfies

canShowConcrete :: ConcreteClassifier a -> Dict Show a
canShowConcrete = concreteSatisfies

canCompareConcrete :: ConcreteClassifier a -> Dict Eq a
canCompareConcrete = concreteSatisfies

{-------------------------------------------------------------------------------
  Size of the classifier

  Mostly used for sanity checking the generator
-------------------------------------------------------------------------------}

sizeUser :: ClassifyUser a -> Int
sizeUser = go
  where
    go :: ClassifyUser a -> Int
    go  C_Simple    = 1
    go (C_NonRec c) = 1 + goElems c
    go (C_Rec    c) = 1 + goElems c
    go  C_Unlifted  = 1

    goElems :: SListI as => Elems ClassifyUser as -> Int
    goElems (Elems cs) = sum . hcollapse $ hmap (K . goElem) cs

    goElem :: Elem ClassifyUser a -> Int
    goElem NoElem   = 0
    goElem (Elem c) = sizeConcrete c

sizeConcrete :: ConcreteClassifier a -> Int
sizeConcrete = classifierSize_ sizeUser

{-------------------------------------------------------------------------------
  Same classifier
-------------------------------------------------------------------------------}

-- | Check that two classifiers are the same
sameConcrete ::
     ConcreteClassifier a
  -> ConcreteClassifier b
  -> Maybe (a :~: b)
sameConcrete = sameClassifier_ sameUser

sameUser :: ClassifyUser a -> ClassifyUser b -> Maybe (a :~: b)
sameUser = go
  where
    go :: ClassifyUser a -> ClassifyUser b -> Maybe (a :~: b)
    go  C_Simple     C_Simple     = Just Refl
    go (C_NonRec c) (C_NonRec c') = sameElems sameUser c c' $ Refl
    go (C_Rec    c) (C_Rec    c') = sameElems sameUser c c' $ Refl
    go  C_Unlifted   C_Unlifted   = Just Refl
    go  _            _            = Nothing

    _checkAllCases :: ClassifyUser a -> ()
    _checkAllCases = \case
        C_Simple{}   -> ()
        C_NonRec{}   -> ()
        C_Rec{}      -> ()
        C_Unlifted{} -> ()

{-------------------------------------------------------------------------------
  Arbitrary
-------------------------------------------------------------------------------}

arbitraryUser :: SizedGen (Some (DepGen ClassifyUser))
arbitraryUser = SG.leafOrStep leaf compound
  where
    leaf :: Gen (Some (DepGen ClassifyUser))
    leaf = QC.oneof [
          -- SimpleType
          pure . Some $ arbitraryDepGen C_Simple

          -- ContainsUnlifted
        , pure . Some $ arbitraryDepGen C_Unlifted
        ]

    compound :: [SizedGen (Some (DepGen ClassifyUser))]
    compound = [
          -- NonRecursive
          go_U_K C_NonRec (NR1 1234)
            (mapSome (GenK (fmap (NR2 True))) <$> arbitraryConcrete)

          -- Recursive
        , go_U_K C_Rec RNil
            (mapSome (GenK (SG.genListLike recursiveFromList)) <$> arbitraryConcrete)
        ]

    go_U_K ::
         ( forall x. Show x => Show (f x)
         , forall x. Eq   x => Eq   (f x)
         )
      => (forall a. Elems ClassifyUser '[a] -> ClassifyUser (f a))
      -> f Void
      -> SizedGen (Some (GenK ConcreteClassifier f))
      -> SizedGen (Some (DepGen ClassifyUser))
    go_U_K cf nothing just =
        SG.leafOrStep
          (pure $ Some $ DepGen (cf ElemU) (pure nothing))
          [(\(Some a) -> Some (genJust (cf . ElemK) a)) <$> just]

arbitraryConcrete :: SizedGen (Some (DepGen ConcreteClassifier))
arbitraryConcrete = arbitraryClassifier_ arbitraryUser