{-# LANGUAGE CPP #-}
-- | A compat module to take fixed points in 'Q'.
module TH.FixQ (fixQ) where

#if MIN_VERSION_template_haskell(2,17,0)
import Control.Monad.Fix (mfix)
import Language.Haskell.TH.Syntax (Q (..))

fixQ :: (a -> Q a) -> Q a
fixQ :: forall a. (a -> Q a) -> Q a
fixQ = forall (m :: * -> *) a. MonadFix m => (a -> m a) -> m a
mfix

#else

-- We don't have a MonadFix instance for Q
import Control.Concurrent.MVar (newEmptyMVar, readMVar, putMVar)
import Control.Exception (BlockedIndefinitelyOnMVar (..), catch, throwIO)
import Control.Exception.Base (FixIOException (..))
import Language.Haskell.TH.Syntax (Q (..), runIO)
import GHC.IO.Unsafe (unsafeDupableInterleaveIO)

fixQ :: (a -> Q a) -> Q a
fixQ k = do
  m <- runIO newEmptyMVar
  ans <- runIO (unsafeDupableInterleaveIO
           (readMVar m `catch` \BlockedIndefinitelyOnMVar ->
                                  throwIO FixIOException))
  result <- k ans
  runIO (putMVar m result)
  return result

#endif