packages feed

lsm-tree-1.0.0.1: src-extras/Database/LSMTree/Extras/NoThunks.hs

{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE UndecidableInstances  #-}

{-# OPTIONS_GHC -Wno-orphans #-}

-- | 'NoThunks' orphan instances
module Database.LSMTree.Extras.NoThunks (
    assertNoThunks
  , propUnsafeNoThunks
  , propNoThunks
  , NoThunksIOLike
  ) where

import           Control.Concurrent.Class.MonadMVar.Strict
import           Control.Concurrent.Class.MonadSTM.RWVar
import           Control.Concurrent.Class.MonadSTM.Strict
import           Control.Exception
import           Control.Monad.Primitive
import           Control.Monad.ST.Unsafe (unsafeIOToST, unsafeSTToIO)
import           Control.RefCount
import           Control.Tracer
import           Data.Bit
import           Data.Map.Strict
import           Data.Primitive
import           Data.Primitive.PrimVar
import           Data.Proxy
import           Data.STRef
import           Data.Typeable
import qualified Data.Vector.Mutable as VM
import qualified Data.Vector.Primitive as VP
import qualified Data.Vector.Unboxed.Mutable as VUM
import           Data.Word
import           Database.LSMTree.Internal.Arena
import           Database.LSMTree.Internal.BlobFile
import           Database.LSMTree.Internal.BlobRef
import           Database.LSMTree.Internal.BloomFilter (Bloom, MBloom)
import           Database.LSMTree.Internal.ChecksumHandle
import           Database.LSMTree.Internal.Chunk
import           Database.LSMTree.Internal.Config
import           Database.LSMTree.Internal.CRC32C
import           Database.LSMTree.Internal.Entry
import           Database.LSMTree.Internal.IncomingRun
import           Database.LSMTree.Internal.Index
import           Database.LSMTree.Internal.Index.Compact
import           Database.LSMTree.Internal.Index.CompactAcc
import           Database.LSMTree.Internal.Index.Ordinary
import           Database.LSMTree.Internal.Index.OrdinaryAcc
import           Database.LSMTree.Internal.Merge
import qualified Database.LSMTree.Internal.Merge as Merge
import           Database.LSMTree.Internal.MergeSchedule
import           Database.LSMTree.Internal.MergingRun
import           Database.LSMTree.Internal.MergingTree
import           Database.LSMTree.Internal.MergingTree.Lookup (LookupTree (..))
import           Database.LSMTree.Internal.Page
import           Database.LSMTree.Internal.PageAcc
import           Database.LSMTree.Internal.Paths
import           Database.LSMTree.Internal.RawBytes
import           Database.LSMTree.Internal.RawOverflowPage
import           Database.LSMTree.Internal.RawPage
import           Database.LSMTree.Internal.Readers
import           Database.LSMTree.Internal.Run
import           Database.LSMTree.Internal.RunAcc
import           Database.LSMTree.Internal.RunBuilder
import           Database.LSMTree.Internal.RunNumber
import           Database.LSMTree.Internal.RunReader hiding (Entry)
import qualified Database.LSMTree.Internal.RunReader as Reader
import           Database.LSMTree.Internal.Serialise
import           Database.LSMTree.Internal.Types as Types
import           Database.LSMTree.Internal.UniqCounter
import           Database.LSMTree.Internal.Unsafe as Unsafe
import           Database.LSMTree.Internal.Unsliced
import           Database.LSMTree.Internal.Vector.Growing
import           Database.LSMTree.Internal.WriteBuffer
import           Database.LSMTree.Internal.WriteBufferBlobs
import           GHC.Generics
import           KMerge.Heap
import           NoThunks.Class
import           System.FS.API
import           System.FS.BlockIO.API
import           System.FS.IO
import           System.FS.Sim.MockFS
import           Test.QuickCheck (Property, Testable (..), counterexample)
import           Unsafe.Coerce

assertNoThunks :: NoThunks a => a -> b -> b
assertNoThunks x = assert p
  where p = case unsafeNoThunks x of
              Nothing -> True
              Just thunkInfo -> error $ "Assertion failed: found thunk" <> show thunkInfo

propUnsafeNoThunks :: NoThunks a => a -> Property
propUnsafeNoThunks x =
    case unsafeNoThunks x of
      Nothing        -> property True
      Just thunkInfo -> counterexample ("Found thunk " <> show thunkInfo) False

propNoThunks :: NoThunks a => a -> IO Property
propNoThunks x = do
    thunkInfoMay <- noThunks [] x
    pure $ case thunkInfoMay of
      Nothing        -> property True
      Just thunkInfo -> counterexample ("Found thunk " <> show thunkInfo) False

{-------------------------------------------------------------------------------
  Public API
-------------------------------------------------------------------------------}

-- | Also checks 'NoThunks' for the tables that are known to be open in the session.
instance (NoThunksIOLike m, Typeable m, Typeable (PrimState m))
      => NoThunks (Types.Session m) where
  showTypeOf (_ :: Proxy (Types.Session m)) = "Database.LSMTree.Session"
  wNoThunks ctx (Types.Session s) = wNoThunks ctx s

-- | Does not check 'NoThunks' for the session that this table belongs to.
instance (NoThunksIOLike m, Typeable m, Typeable (PrimState m))
      => NoThunks (Types.Table m k v b) where
  showTypeOf (_ :: Proxy (Types.Table m k v b)) = "Database.LSMTree.Table"
  wNoThunks ctx (Types.Table t) = wNoThunks ctx t

{-------------------------------------------------------------------------------
  Unsafe
-------------------------------------------------------------------------------}

deriving stock instance Generic (Unsafe.Session m h)
-- | Also checks 'NoThunks' for the 'Unsafe.Table's that are known to be
-- open in the 'Unsafe.Session'.
deriving anyclass instance (NoThunksIOLike m, Typeable m, Typeable h, Typeable (PrimState m))
                        => NoThunks (Unsafe.Session m h)

deriving stock instance Generic (SessionState m h)
deriving anyclass instance (NoThunksIOLike m, Typeable m, Typeable h, Typeable (PrimState m))
                        => NoThunks (SessionState m h)

deriving stock instance Generic (SessionEnv m h)
deriving anyclass instance (NoThunksIOLike m, Typeable m, Typeable h, Typeable (PrimState m))
                        => NoThunks (SessionEnv m h)

deriving stock instance Generic (Unsafe.Table m h)
-- | Does not check 'NoThunks' for the 'Unsafe.Session' that this
-- 'Unsafe.Table' belongs to.
deriving via AllowThunksIn '["tableSession"] (Unsafe.Table m h)
    instance (NoThunksIOLike m, Typeable m, Typeable h, Typeable (PrimState m))
          => NoThunks (Unsafe.Table m h)

deriving stock instance Generic (TableState m h)
deriving anyclass instance (NoThunksIOLike m, Typeable m, Typeable h, Typeable (PrimState m))
                        => NoThunks (TableState m h)

deriving stock instance Generic (TableEnv m h)
deriving via AllowThunksIn '["tableSessionEnv"] (TableEnv m h)
    instance (NoThunksIOLike m, Typeable m, Typeable h, Typeable (PrimState m))
          => NoThunks (TableEnv m h)

-- | Does not check 'NoThunks' for the 'Unsafe.Session' that this
-- 'Unsafe.Cursor' belongs to.
deriving stock instance Generic (Unsafe.Cursor m h)
deriving anyclass instance (NoThunksIOLike m, Typeable m, Typeable h, Typeable (PrimState m))
                        => NoThunks (Unsafe.Cursor m h)

deriving stock instance Generic (CursorState m h)
deriving anyclass instance (NoThunksIOLike m, Typeable m, Typeable h, Typeable (PrimState m))
                        => NoThunks (CursorState m h)

deriving stock instance Generic (CursorEnv m h)
deriving via AllowThunksIn ["cursorSession", "cursorSessionEnv"] (CursorEnv m h)
    instance (Typeable m, Typeable h, Typeable (PrimState m))
          => NoThunks (CursorEnv m h)

deriving stock instance Generic TableId
deriving anyclass instance NoThunks TableId

deriving stock instance Generic CursorId
deriving anyclass instance NoThunks CursorId

{-------------------------------------------------------------------------------
  UniqCounter
-------------------------------------------------------------------------------}

deriving stock instance Generic (UniqCounter m)
deriving anyclass instance (NoThunks (PrimVar (PrimState m) Int))
                        => NoThunks (UniqCounter m)

{-------------------------------------------------------------------------------
  Serialise
-------------------------------------------------------------------------------}

deriving stock instance Generic RawBytes
deriving anyclass instance NoThunks RawBytes

deriving stock instance Generic SerialisedKey
deriving anyclass instance NoThunks SerialisedKey

deriving stock instance Generic SerialisedValue
deriving anyclass instance NoThunks SerialisedValue

deriving stock instance Generic SerialisedBlob
deriving anyclass instance NoThunks SerialisedBlob

instance NoThunks (Unsliced a) where
  showTypeOf (_ :: Proxy (Unsliced a)) = "Unsliced"
  wNoThunks ctx (x :: Unsliced a) = noThunks ctx y
    where
      -- Unsliced is a newtype around a ByteArray, so we can unsafeCoerce
      -- safely. The bang pattern will only evaluate the coercion, because the
      -- byte array is already in WHNF.
      y :: ByteArray
      !y = unsafeCoerce x

{-------------------------------------------------------------------------------
  Run
-------------------------------------------------------------------------------}

deriving stock instance Generic (Run m h)
deriving anyclass instance (Typeable m, Typeable (PrimState m), Typeable h)
                        => NoThunks (Run m h)

deriving stock instance Generic RunParams
deriving anyclass instance NoThunks RunParams

deriving stock instance Generic RunBloomFilterAlloc
deriving anyclass instance NoThunks RunBloomFilterAlloc

deriving stock instance Generic RunDataCaching
deriving anyclass instance NoThunks RunDataCaching

deriving stock instance Generic IndexType
deriving anyclass instance NoThunks IndexType

{-------------------------------------------------------------------------------
  Paths
-------------------------------------------------------------------------------}

deriving stock instance Generic RunNumber
deriving anyclass instance NoThunks RunNumber

deriving stock instance Generic SessionRoot
deriving anyclass instance NoThunks SessionRoot

deriving stock instance Generic RunFsPaths
deriving anyclass instance NoThunks RunFsPaths

deriving stock instance Generic (ForKOps a)
deriving anyclass instance NoThunks a => NoThunks (ForKOps a)

deriving stock instance Generic (ForBlob a)
deriving anyclass instance NoThunks a => NoThunks (ForBlob a)

deriving stock instance Generic (ForFilter a)
deriving anyclass instance NoThunks a => NoThunks (ForFilter a)

deriving stock instance Generic (ForIndex a)
deriving anyclass instance NoThunks a => NoThunks (ForIndex a)

deriving stock instance Generic (ForRunFiles a)
deriving anyclass instance NoThunks a => NoThunks (ForRunFiles a)

{-------------------------------------------------------------------------------
  CRC32C
-------------------------------------------------------------------------------}

deriving stock instance Generic CRC32C
deriving anyclass instance NoThunks CRC32C

{-------------------------------------------------------------------------------
  WriteBuffer
-------------------------------------------------------------------------------}

instance NoThunks WriteBuffer where
  showTypeOf (_ :: Proxy WriteBuffer) = "WriteBuffer"
  wNoThunks ctx (x :: WriteBuffer) = noThunks ctx y
    where
      -- toMap simply unwraps the WriteBuffer newtype wrapper. The bang pattern
      -- will only evaluate the coercion, because the inner Map is already in
      -- WHNF.
      y :: Map SerialisedKey (Entry SerialisedValue BlobSpan)
      !y = toMap x

{-------------------------------------------------------------------------------
  BlobFile
-------------------------------------------------------------------------------}

deriving stock instance Generic (WriteBufferBlobs m h)
deriving anyclass instance (Typeable (PrimState m), Typeable m, Typeable h)
                        => NoThunks (WriteBufferBlobs m h)

deriving stock instance Generic (FilePointer m)
deriving anyclass instance Typeable (PrimState m)
                        => NoThunks (FilePointer m)

{-------------------------------------------------------------------------------
  Index
-------------------------------------------------------------------------------}

deriving stock instance Generic IndexCompact
deriving anyclass instance NoThunks IndexCompact

deriving stock instance Generic PageNo
deriving anyclass instance NoThunks PageNo

deriving stock instance Generic IndexOrdinary
deriving anyclass instance NoThunks IndexOrdinary

deriving stock instance Generic Index
deriving anyclass instance NoThunks Index

{-------------------------------------------------------------------------------
  MergeSchedule
-------------------------------------------------------------------------------}

deriving stock instance Generic (TableContent m h)
deriving anyclass instance
  ( Typeable m, Typeable (PrimState m), Typeable h
  , NoThunks (StrictMVar m (MergingRunState LevelMergeType m h))
  , NoThunks (StrictMVar m (MergingTreeState m h))
  ) => NoThunks (TableContent m h)

deriving stock instance Generic (LevelsCache m h)
deriving anyclass instance
  (Typeable m, Typeable (PrimState m), Typeable h)
  => NoThunks (LevelsCache m h)

deriving stock instance Generic (Level m h)
deriving anyclass instance
  ( Typeable m, Typeable (PrimState m), Typeable h
  , NoThunks (StrictMVar m (MergingRunState LevelMergeType m h))
  ) => NoThunks (Level m h)

deriving stock instance Generic (IncomingRun m h)
deriving anyclass instance
  ( Typeable m, Typeable (PrimState m), Typeable h
  , NoThunks (StrictMVar m (MergingRunState LevelMergeType m h))
  ) => NoThunks (IncomingRun m h)

deriving stock instance Generic (UnionLevel m h)
deriving anyclass instance
  ( Typeable m, Typeable (PrimState m), Typeable h
  , NoThunks (StrictMVar m (MergingTreeState m h))
  ) => NoThunks (UnionLevel m h)

deriving stock instance Generic (UnionCache m h)
deriving anyclass instance
  ( Typeable m, Typeable (PrimState m), Typeable h
  ) => NoThunks (UnionCache m h)

deriving stock instance Generic MergePolicyForLevel
deriving anyclass instance NoThunks MergePolicyForLevel

deriving stock instance Generic NominalDebt
deriving anyclass instance NoThunks NominalDebt

deriving stock instance Generic NominalCredits
deriving anyclass instance NoThunks NominalCredits

{-------------------------------------------------------------------------------
  MergingRun
-------------------------------------------------------------------------------}

deriving stock instance Generic (MergingRun t m h)
deriving anyclass instance ( Typeable m, Typeable (PrimState m), Typeable h
                           , NoThunks (StrictMVar m (MergingRunState t m h))
                           ) => NoThunks (MergingRun t m h)

deriving stock instance Generic (MergingRunState t m h)
deriving anyclass instance ( Typeable m, Typeable (PrimState m), Typeable h
                           , NoThunks t
                           ) => NoThunks (MergingRunState t m h)

deriving stock instance Generic MergeDebt
deriving anyclass instance NoThunks MergeDebt

deriving stock instance Generic MergeCredits
deriving anyclass instance NoThunks MergeCredits

deriving stock instance Generic (CreditsVar s)
deriving anyclass instance Typeable s => NoThunks (CreditsVar s)

deriving stock instance Generic MergeKnownCompleted
deriving anyclass instance NoThunks MergeKnownCompleted

{-------------------------------------------------------------------------------
  MergingTree
-------------------------------------------------------------------------------}

deriving stock instance Generic (MergingTree m h)
deriving anyclass instance
  ( Typeable m, Typeable (PrimState m), Typeable h
  , NoThunks (StrictMVar m (MergingTreeState m h))
  ) => NoThunks (MergingTree m h)

deriving stock instance Generic (MergingTreeState m h)
deriving anyclass instance
  ( Typeable m, Typeable (PrimState m), Typeable h
  , NoThunks (StrictMVar m (MergingRunState LevelMergeType m h))
  , NoThunks (StrictMVar m (MergingRunState TreeMergeType m h))
  , NoThunks (StrictMVar m (MergingTreeState m h))
  ) => NoThunks (MergingTreeState m h)

deriving stock instance Generic (PendingMerge m h)
deriving anyclass instance
  ( Typeable m, Typeable (PrimState m), Typeable h
  , NoThunks (StrictMVar m (MergingRunState LevelMergeType m h))
  , NoThunks (StrictMVar m (MergingTreeState m h))
  ) => NoThunks (PendingMerge m h)

deriving stock instance Generic (PreExistingRun m h)
deriving anyclass instance
  ( Typeable m, Typeable (PrimState m), Typeable h
  , NoThunks (StrictMVar m (MergingRunState LevelMergeType m h))
  ) => NoThunks (PreExistingRun m h)

deriving stock instance Generic (LookupTree a)
deriving anyclass instance NoThunks a => NoThunks (LookupTree a)

{-------------------------------------------------------------------------------
  Entry
-------------------------------------------------------------------------------}

deriving stock instance Generic (Entry v b)
deriving anyclass instance (NoThunks v, NoThunks b)
                        => NoThunks (Entry v b)

deriving stock instance Generic NumEntries
deriving anyclass instance NoThunks NumEntries

{-------------------------------------------------------------------------------
  RunBuilder
-------------------------------------------------------------------------------}

deriving stock instance Generic (RunBuilder m h)
deriving anyclass instance (Typeable m, Typeable (PrimState m), Typeable h)
                        => NoThunks (RunBuilder m h)

deriving stock instance Generic (ChecksumHandle s h)
deriving anyclass instance (Typeable s, Typeable h)
                        => NoThunks (ChecksumHandle s h)

{-------------------------------------------------------------------------------
  RunAcc
-------------------------------------------------------------------------------}

deriving stock instance Generic (RunAcc s)
deriving anyclass instance Typeable s
                        => NoThunks (RunAcc s)

{-------------------------------------------------------------------------------
  IndexAcc
-------------------------------------------------------------------------------}

deriving stock instance Generic (IndexCompactAcc s)
deriving anyclass instance Typeable s
                        => NoThunks (IndexCompactAcc s)

deriving stock instance Generic (SMaybe a)
deriving anyclass instance NoThunks a => NoThunks (SMaybe a)

deriving stock instance Generic (IndexOrdinaryAcc s)
deriving anyclass instance Typeable s
                        => NoThunks (IndexOrdinaryAcc s)

deriving stock instance Generic (IndexAcc s)
deriving anyclass instance Typeable s
                        => NoThunks (IndexAcc s)

{-------------------------------------------------------------------------------
  GrowingVector
-------------------------------------------------------------------------------}

instance (NoThunks a, Typeable s, Typeable a) => NoThunks (GrowingVector s a) where
  showTypeOf (p :: Proxy (GrowingVector s a)) = show $ typeRep p
  wNoThunks ctx
    (GrowingVector (a :: STRef s (VM.MVector s a)) (b :: PrimVar s Int))
    = allNoThunks [
          noThunks ctx b
          -- Check that the STRef is in WHNF
        , noThunks ctx $ OnlyCheckWhnf a
          -- Check that the MVector is in WHNF
        , do
            mvec <- unsafeSTToIO $ readSTRef a
            noThunks ctx' $ OnlyCheckWhnf mvec
          -- Check that the vector elements contain no thunks. The vector
          -- contains undefined elements after the first @n@ elements
        , do
            n <- unsafeSTToIO $ readPrimVar b
            mvec <- unsafeSTToIO $ readSTRef a
            allNoThunks [
                unsafeSTToIO (VM.read mvec i) >>= \x -> noThunks ctx'' x
              | i <- [0..n-1]
              ]
        ]
    where
      ctx' = showTypeOf (Proxy @(STRef s (VM.MVector s a))) : ctx
      ctx'' = showTypeOf (Proxy @(VM.MVector s a)) : ctx'

{-------------------------------------------------------------------------------
  Baler
-------------------------------------------------------------------------------}

deriving stock instance Generic (Baler s)
deriving anyclass instance Typeable s
                        => NoThunks (Baler s)

{-------------------------------------------------------------------------------
  PageAcc
-------------------------------------------------------------------------------}

deriving stock instance Generic (PageAcc s)
deriving anyclass instance Typeable s
                        => NoThunks (PageAcc s)

{-------------------------------------------------------------------------------
  Merge
-------------------------------------------------------------------------------}

deriving stock instance Generic (Merge t m h)
deriving anyclass instance ( Typeable m, Typeable (PrimState m), Typeable h
                           , NoThunks t
                           ) => NoThunks (Merge t m h)

deriving stock instance Generic MergeType
deriving anyclass instance NoThunks MergeType

deriving stock instance Generic LevelMergeType
deriving anyclass instance NoThunks LevelMergeType

deriving stock instance Generic TreeMergeType
deriving anyclass instance NoThunks TreeMergeType

deriving stock instance Generic Merge.StepResult
deriving anyclass instance NoThunks Merge.StepResult

deriving stock instance Generic Merge.MergeState
deriving anyclass instance NoThunks Merge.MergeState

{-------------------------------------------------------------------------------
  Readers
-------------------------------------------------------------------------------}

deriving stock instance Generic (Readers m h)
deriving anyclass instance (Typeable m, Typeable (PrimState m), Typeable h)
                        => NoThunks (Readers m h)

deriving stock instance Generic (Reader m h)
instance (Typeable m, Typeable (PrimState m), Typeable h)
      => NoThunks (Reader m h) where
  showTypeOf (_ :: Proxy (Reader m h)) = "Reader"
  wNoThunks ctx = \case
    ReadRun r      -> noThunks ctx r
    ReadBuffer var -> noThunks ctx (OnlyCheckWhnf var) -- contents intentionally lazy
    ReadReaders ty readers -> allNoThunks [
        noThunks ctx ty
      , noThunks ctx readers
      ]

deriving stock instance Generic ReadersMergeType
deriving anyclass instance NoThunks ReadersMergeType

deriving stock instance Generic HasMore
deriving anyclass instance NoThunks HasMore

deriving stock instance Generic ReaderNumber
deriving anyclass instance NoThunks ReaderNumber

deriving stock instance Generic (ReadCtx m h)
deriving anyclass instance (Typeable m, Typeable (PrimState m), Typeable h)
                        => NoThunks (ReadCtx m h)

{-------------------------------------------------------------------------------
  Reader
-------------------------------------------------------------------------------}

deriving stock instance Generic (RunReader m h)
deriving anyclass instance (Typeable m, Typeable (PrimState m), Typeable h)
                        => NoThunks (RunReader m h)

-- | Allows thunks in the overflow pages
instance ( Typeable m, Typeable (PrimState m), Typeable h
         ) => NoThunks (Reader.Entry m h) where
  showTypeOf (p :: Proxy (Reader.Entry m h)) = show $ typeRep p
  wNoThunks ctx (Reader.Entry (e :: Entry SerialisedValue (RawBlobRef m h))) = noThunks ctx e
  wNoThunks ctx (EntryOverflow
    (entryPrefix :: Entry SerialisedValue (RawBlobRef m h))
    (page :: RawPage)
    (len :: Word32)
    (overflowPages :: [RawOverflowPage]) ) =
      allNoThunks [
          noThunks ctx entryPrefix
        , noThunks ctx page
        , noThunks ctx len
        , noThunks ctx (OnlyCheckWhnf overflowPages)
        ]

{-------------------------------------------------------------------------------
  RawPage
-------------------------------------------------------------------------------}

deriving stock instance Generic RawPage
deriving anyclass instance NoThunks RawPage

{-------------------------------------------------------------------------------
  RawPage
-------------------------------------------------------------------------------}

deriving stock instance Generic RawOverflowPage
deriving anyclass instance NoThunks RawOverflowPage

{-------------------------------------------------------------------------------
  BlobRef
-------------------------------------------------------------------------------}

deriving stock instance Generic BlobSpan
deriving anyclass instance NoThunks BlobSpan

deriving stock instance Generic (BlobFile m h)
deriving anyclass instance (Typeable h, Typeable (PrimState m))
                        => NoThunks (BlobFile m h)

deriving stock instance Generic (RawBlobRef m h)
deriving anyclass instance (Typeable h, Typeable (PrimState m))
                        => NoThunks (RawBlobRef m h)

deriving stock instance Generic (WeakBlobRef m h)
deriving anyclass instance (Typeable h, Typeable m, Typeable (PrimState m))
                        => NoThunks (WeakBlobRef m h)

{-------------------------------------------------------------------------------
  Arena
-------------------------------------------------------------------------------}

-- TODO: proper instance
deriving via OnlyCheckWhnf (ArenaManager m)
    instance Typeable m => NoThunks (ArenaManager m)

{-------------------------------------------------------------------------------
  Config
-------------------------------------------------------------------------------}

deriving stock instance Generic TableConfig
deriving anyclass instance NoThunks TableConfig

deriving stock instance Generic MergePolicy
deriving anyclass instance NoThunks MergePolicy

deriving stock instance Generic SizeRatio
deriving anyclass instance NoThunks SizeRatio

deriving stock instance Generic WriteBufferAlloc
deriving anyclass instance NoThunks WriteBufferAlloc

deriving stock instance Generic BloomFilterAlloc
deriving anyclass instance NoThunks BloomFilterAlloc

deriving stock instance Generic FencePointerIndexType
deriving anyclass instance NoThunks FencePointerIndexType

deriving stock instance Generic DiskCachePolicy
deriving anyclass instance NoThunks DiskCachePolicy

deriving stock instance Generic MergeSchedule
deriving anyclass instance NoThunks MergeSchedule

deriving stock instance Generic MergeBatchSize
deriving anyclass instance NoThunks MergeBatchSize

{-------------------------------------------------------------------------------
  RWVar
-------------------------------------------------------------------------------}

deriving stock instance Generic (RWVar m a)
deriving anyclass instance NoThunks (StrictTVar m (RWState a)) => NoThunks (RWVar m a)

deriving stock instance Generic (RWState a)
deriving anyclass instance NoThunks a => NoThunks (RWState a)

{-------------------------------------------------------------------------------
  RefCounter
-------------------------------------------------------------------------------}

instance Typeable (PrimState m) => NoThunks (RefCounter m) where
  showTypeOf (_ :: Proxy (RefCounter m)) = "RefCounter"
  wNoThunks ctx
    (RefCounter (a :: PrimVar (PrimState m) Int) (b :: m ()))
    = allNoThunks [
          noThunks ctx a
        , noThunks ctx $ (OnlyCheckWhnfNamed b :: OnlyCheckWhnfNamed "finaliser" (m ()))
        ]

-- RefCtx constructor not exported
deriving via OnlyCheckWhnf RefCtx instance NoThunks RefCtx

-- Ref constructor not exported, cannot derive Generic, use DeRef instead.
instance (NoThunks obj, Typeable obj) => NoThunks (Ref obj) where
  showTypeOf p@(_ :: Proxy (Ref obj)) = show $ typeRep p
  wNoThunks ctx (DeRef ref) = noThunks ctx ref

deriving stock instance Generic (WeakRef obj)
deriving anyclass instance (NoThunks obj, Typeable obj) => NoThunks (WeakRef obj)

{-------------------------------------------------------------------------------
  kmerge
-------------------------------------------------------------------------------}

instance (NoThunks a, Typeable s, Typeable a) => NoThunks (MutableHeap s a) where
  showTypeOf (p :: Proxy (MutableHeap s a)) = show $ typeRep p
  wNoThunks ctx
    (MH (a :: PrimVar s Int) (b :: SmallMutableArray s a))
    = allNoThunks [
          noThunks ctx a
          -- Check that the array is in WHNF
        , noThunks ctx (OnlyCheckWhnf b)
          -- Check that the array elements contain no thunks. The small array
          -- may contain undefined placeholder values after the first @n@
          -- elements in the array. The very first element of the array can also
          -- be undefined.
        , do
            n <- unsafeSTToIO (readPrimVar a)
            allNoThunks [
                unsafeSTToIO (readSmallArray b i) >>= \x -> noThunks ctx' x
              | i <- [1..n-1]
              ]
        ]
    where
      ctx' = showTypeOf (Proxy @(SmallMutableArray s a)) : ctx

{-------------------------------------------------------------------------------
  IOLike
-------------------------------------------------------------------------------}

-- | 'NoThunks' constraints for IO-like monads
--
-- Some constraints, like @NoThunks (MutVar s a)@ and @NoThunks (StrictTVar m
-- a)@, can not be satisfied for arbitrary @m@\/@s@, and must be instantiated
-- for a concrete @m@\/@s@, like @IO@\/@RealWorld@.
class ( forall a. (NoThunks a, Typeable a) => NoThunks (StrictTVar m a)
      , forall a. (NoThunks a, Typeable a) => NoThunks (StrictMVar m a)
      ) => NoThunksIOLike' m s

instance NoThunksIOLike' IO RealWorld

type NoThunksIOLike m = NoThunksIOLike' m (PrimState m)

instance (NoThunks a, Typeable a) => NoThunks (StrictTVar IO a) where
  showTypeOf (p :: Proxy (StrictTVar IO a)) = show $ typeRep p
  wNoThunks _ctx _var = do
    x <- readTVarIO _var
    noThunks _ctx x

-- TODO: in some cases, strict-mvar functions leave thunks behind, in particular
-- modifyMVarMasked and modifyMVarMasked_. So in some specific cases we evaluate
-- the contents of the MVar to WHNF, and keep checking nothunks from there. See
-- lsm-tree#444.
--
-- TODO: we tried using overlapping instances for @StrictMVar IO a@ and
-- @StrictMVar IO (MergingRunState IO h)@, but the quantified constraint in
-- NoThunksIOLike' will throw a compiler error telling us to mark the instances
-- for StrictMVar as incoherent. Marking them as incoherent makes the tests
-- fail... We are unsure if it can be overcome, but the current casting approach
-- works, so there is no priority to use rewrite this code to use overlapping
-- instances.
instance (NoThunks a, Typeable a) => NoThunks (StrictMVar IO a) where
  showTypeOf (p :: Proxy (StrictMVar IO a)) = show $ typeRep p
  wNoThunks ctx var
    -- TODO: Revisit which of these cases are still needed.
    | Just (Proxy :: Proxy (MergingRunState LevelMergeType IO HandleIO))
        <- gcast (Proxy @a)
    = workAroundCheck
    | Just (Proxy :: Proxy (MergingRunState TreeMergeType IO HandleIO))
        <- gcast (Proxy @a)
    = workAroundCheck
    | Just (Proxy :: Proxy (MergingRunState LevelMergeType IO HandleMock))
        <- gcast (Proxy @a)
    = workAroundCheck
    | Just (Proxy :: Proxy (MergingRunState TreeMergeType IO HandleMock))
        <- gcast (Proxy @a)
    = workAroundCheck
    | otherwise
    = properCheck
    where
      properCheck = do
        x <- readMVar var
        noThunks ctx x

      workAroundCheck = do
        !x <- readMVar var
        noThunks ctx x

{-------------------------------------------------------------------------------
  vector
-------------------------------------------------------------------------------}

-- TODO: upstream to @nothunks@
instance (NoThunks a, Typeable s, Typeable a) => NoThunks (VM.MVector s a) where
    showTypeOf (p :: Proxy (VM.MVector s a)) = show $ typeRep p
    wNoThunks ctx v =
      allNoThunks [
          unsafeSTToIO (VM.read v i >>= \ x -> unsafeIOToST (noThunks ctx x))
        | i <- [0.. VM.length v-1]
        ]

-- TODO: https://github.com/input-output-hk/nothunks/issues/57
deriving via OnlyCheckWhnf (VP.Vector a)
    instance Typeable a => NoThunks (VP.Vector a)

-- TODO: upstream to @nothunks@
deriving via OnlyCheckWhnf (VUM.MVector s Word64)
    instance Typeable s => NoThunks (VUM.MVector s Word64)

-- TODO: upstream to @nothunks@
deriving via OnlyCheckWhnf (VUM.MVector s Bit)
    instance Typeable s => NoThunks (VUM.MVector s Bit)

-- TODO: upstream to @nothunks@
deriving via OnlyCheckWhnf (VP.MVector s Word8)
    instance Typeable s => NoThunks (VP.MVector s Word8)

{-------------------------------------------------------------------------------
  ST
-------------------------------------------------------------------------------}

-- TODO: upstream to @nothunks@
instance NoThunks a => NoThunks (STRef s a) where
  showTypeOf (_ :: Proxy (STRef s a)) = "STRef"
  wNoThunks ctx ref = do
    x <- unsafeSTToIO $ readSTRef ref
    noThunks ctx x

{-------------------------------------------------------------------------------
  primitive
-------------------------------------------------------------------------------}

-- TODO: https://github.com/input-output-hk/nothunks/issues/56
instance NoThunks a => NoThunks (MutVar s a) where
  showTypeOf (_ :: Proxy (MutVar s a)) = "MutVar"
  wNoThunks ctx var = do
      x <- unsafeSTToIO $ readMutVar var
      noThunks ctx x

-- TODO: https://github.com/input-output-hk/nothunks/issues/56
deriving via OnlyCheckWhnf (PrimVar s a)
    instance (Typeable s, Typeable a) => NoThunks (PrimVar s a)

-- TODO: https://github.com/input-output-hk/nothunks/issues/56
instance NoThunks a => NoThunks (SmallMutableArray s a) where
  showTypeOf (_ :: Proxy (SmallMutableArray s a)) = "SmallMutableArray"
  wNoThunks ctx arr = do
      n <- unsafeSTToIO $ getSizeofSmallMutableArray arr
      allNoThunks [
          unsafeSTToIO (readSmallArray arr i) >>= \x -> noThunks ctx x
        | i <- [0..n-1]
        ]

-- TODO: https://github.com/input-output-hk/nothunks/issues/56
deriving via OnlyCheckWhnf (MutablePrimArray s a)
    instance (Typeable s, Typeable a) => NoThunks (MutablePrimArray s a)

-- TODO: https://github.com/input-output-hk/nothunks/issues/56
deriving via OnlyCheckWhnf ByteArray
    instance NoThunks ByteArray

{-------------------------------------------------------------------------------
  bloomfilter
-------------------------------------------------------------------------------}

-- TODO: check heap?
deriving via OnlyCheckWhnf (Bloom a)
    instance Typeable a => NoThunks (Bloom a)

-- TODO: check heap?
deriving via OnlyCheckWhnf (MBloom s a)
    instance (Typeable s, Typeable a) => NoThunks (MBloom s a)

{-------------------------------------------------------------------------------
  fs-api and fs-sim
-------------------------------------------------------------------------------}

-- TODO: check heap?
deriving via OnlyCheckWhnf (HasFS m h)
    instance (Typeable m, Typeable h) => NoThunks (HasFS m h)

-- TODO: check heap?
deriving via OnlyCheckWhnf (Handle h)
    instance Typeable h => NoThunks (Handle h)

-- TODO: check heap?
deriving via OnlyCheckWhnf FsPath
    instance NoThunks FsPath

{-------------------------------------------------------------------------------
  blockio
-------------------------------------------------------------------------------}

-- TODO: check heap?
deriving via OnlyCheckWhnf (HasBlockIO m h)
    instance (Typeable m, Typeable h) => NoThunks (HasBlockIO m h)

-- TODO: check heap?
deriving via OnlyCheckWhnf (LockFileHandle m)
    instance Typeable m => NoThunks (LockFileHandle m)

{-------------------------------------------------------------------------------
  contra-tracer
-------------------------------------------------------------------------------}

-- TODO: check heap?
deriving via OnlyCheckWhnf (Tracer m a)
    instance (Typeable m, Typeable a) => NoThunks (Tracer m a)