packages feed

hnix-0.13.1: src/Nix/Cited/Basic.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE InstanceSigs #-}

module Nix.Cited.Basic where

import           Prelude                 hiding ( force )
import           Control.Comonad                ( Comonad )
import           Control.Comonad.Env            ( ComonadEnv )
import           Control.Monad.Catch     hiding ( catchJust )
import           Nix.Cited
import           Nix.Eval                      as Eval
import           Nix.Exec
import           Nix.Expr
import           Nix.Frames
import           Nix.Options
import           Nix.Thunk
import           Nix.Utils
import           Nix.Value

newtype Cited t f m a = Cited { getCited :: NCited m (NValue t f m) a }
  deriving
    ( Generic
    , Typeable
    , Functor
    , Applicative
    , Foldable
    , Traversable
    , Comonad
    , ComonadEnv [Provenance m (NValue t f m)]
    )

instance HasCitations1 m (NValue t f m) (Cited t f m) where
  citations1 (Cited c) = citations c
  addProvenance1 x (Cited c) = Cited $ addProvenance x c

instance ( Has e Options
         , Framed e m
         , MonadThunk t m v
         , Typeable m
         , Typeable f
         , Typeable u
         , MonadCatch m
         )
  => MonadThunk (Cited u f m t) m v where

  thunk :: m v -> m (Cited u f m t)
  thunk mv = do
    opts :: Options <- asks (view hasLens)

    bool
      (Cited . NCited mempty <$> thunk mv)
      (do
        frames :: Frames <- asks (view hasLens)

        -- Gather the current evaluation context at the time of thunk
        -- creation, and record it along with the thunk.
        let
          go (fromException -> Just (EvaluatingExpr scope (AnnE s e))) =
            let e' = Compose (Ann s (Nothing <$ e)) in
            [Provenance scope e']
          go _ = mempty
          ps = concatMap (go . frame) frames

        Cited . NCited ps <$> thunk mv
      )
      (thunks opts)

  thunkId :: Cited u f m t -> ThunkId m
  thunkId (Cited (NCited _ t)) = thunkId @_ @m t

  queryM :: m v -> Cited u f m t -> m v
  queryM m (Cited (NCited _ t)) = queryM m t

  -- | The ThunkLoop exception is thrown as an exception with MonadThrow,
  --   which does not capture the current stack frame information to provide
  --   it in a NixException, so we catch and re-throw it here using
  --   'throwError' from Frames.hs.
  force :: Cited u f m t -> m v
  force (Cited (NCited ps t)) = handleDisplayProvenance ps $ force t

  forceEff :: Cited u f m t -> m v
  forceEff (Cited (NCited ps t)) = handleDisplayProvenance ps $ forceEff t

  further :: Cited u f m t -> m (Cited u f m t)
  further (Cited (NCited ps t)) = Cited . NCited ps <$> further t


-- * Kleisli functor HOFs

-- Please, do not use MonadThunkF for MonadThunk, later uses more straight-forward specialized line of functions.
instance ( Has e Options
         , Framed e m
         , MonadThunkF t m v
         , Typeable m
         , Typeable f
         , Typeable u
         , MonadCatch m
         )
  => MonadThunkF (Cited u f m t) m v where

  queryMF :: (v -> m r) -> m r -> Cited u f m t -> m r
  queryMF k m (Cited (NCited _ t)) = queryMF k m t

  forceF :: (v -> m r) -> Cited u f m t -> m r
  forceF k (Cited (NCited ps t)) = handleDisplayProvenance ps $ forceF k t

  forceEffF :: (v -> m r) -> Cited u f m t -> m r
  forceEffF k (Cited (NCited ps t)) = handleDisplayProvenance ps $ forceEffF k t

  furtherF :: (m v -> m v) -> Cited u f m t -> m (Cited u f m t)
  furtherF k (Cited (NCited ps t)) = Cited . NCited ps <$> furtherF k t


-- ** Utils

handleDisplayProvenance
  :: (MonadCatch m
    , Typeable m
    , Typeable v
    , Has e Frames
    , MonadReader e m
    )
  => [Provenance m v]
  -> m a
  -> m a
handleDisplayProvenance ps f =
  catch
    (displayProvenance ps f)
    (throwError @ThunkLoop)

displayProvenance
  :: (MonadThrow m
    , MonadReader e m
    , Has e Frames
    , Typeable m
    , Typeable v
    )
  => [Provenance m v]
  -> m a
  -> m a
displayProvenance =
  list
    id
    (\ (Provenance scope e@(Compose (Ann s _)) : _) ->
      withFrame Info (ForcingExpr scope (wrapExprLoc s e))
    )