ghcide-1.6.0.0: src/Development/IDE/Types/Shake.hs
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
module Development.IDE.Types.Shake
( Q (..),
A (..),
Value (..),
ValueWithDiagnostics (..),
Values,
Key (..),
BadDependency (..),
ShakeValue(..),
currentValue,
isBadDependency,
toShakeValue,encodeShakeValue,decodeShakeValue,toKey,toNoFileKey,fromKey,fromKeyType,WithHieDb)
where
import Control.DeepSeq
import Control.Exception
import qualified Data.ByteString.Char8 as BS
import Data.Dynamic
import Data.Hashable
import Data.Typeable (cast)
import Data.Vector (Vector)
import Development.IDE.Core.PositionMapping
import Development.IDE.Graph (Key (..), RuleResult)
import qualified Development.IDE.Graph as Shake
import Development.IDE.Types.Diagnostics
import Development.IDE.Types.Location
import GHC.Generics
import HieDb.Types (HieDb)
import Language.LSP.Types
import qualified StmContainers.Map as STM
import Type.Reflection (SomeTypeRep (SomeTypeRep),
pattern App, pattern Con,
typeOf, typeRep,
typeRepTyCon)
import Unsafe.Coerce (unsafeCoerce)
-- | Intended to represent HieDb calls wrapped with (currently) retry
-- functionality
type WithHieDb = forall a. (HieDb -> IO a) -> IO a
data Value v
= Succeeded TextDocumentVersion v
| Stale (Maybe PositionDelta) TextDocumentVersion v
| Failed Bool -- True if we already tried the persistent rule
deriving (Functor, Generic, Show)
instance NFData v => NFData (Value v)
-- | Convert a Value to a Maybe. This will only return `Just` for
-- up2date results not for stale values.
currentValue :: Value v -> Maybe v
currentValue (Succeeded _ v) = Just v
currentValue (Stale _ _ _) = Nothing
currentValue Failed{} = Nothing
data ValueWithDiagnostics
= ValueWithDiagnostics !(Value Dynamic) !(Vector FileDiagnostic)
-- | The state of the all values and diagnostics
type Values = STM.Map Key ValueWithDiagnostics
-- | When we depend on something that reported an error, and we fail as a direct result, throw BadDependency
-- which short-circuits the rest of the action
newtype BadDependency = BadDependency String deriving Show
instance Exception BadDependency
isBadDependency :: SomeException -> Bool
isBadDependency x
| Just (_ :: BadDependency) <- fromException x = True
| otherwise = False
toKey :: Shake.ShakeValue k => k -> NormalizedFilePath -> Key
toKey = (Key.) . curry Q
fromKey :: Typeable k => Key -> Maybe (k, NormalizedFilePath)
fromKey (Key k)
| Just (Q (k', f)) <- cast k = Just (k', f)
| otherwise = Nothing
-- | fromKeyType (Q (k,f)) = (typeOf k, f)
fromKeyType :: Key -> Maybe (SomeTypeRep, NormalizedFilePath)
fromKeyType (Key k) = case typeOf k of
App (Con tc) a | tc == typeRepTyCon (typeRep @Q)
-> case unsafeCoerce k of
Q (_ :: (), f) -> Just (SomeTypeRep a, f)
_ -> Nothing
toNoFileKey :: (Show k, Typeable k, Eq k, Hashable k) => k -> Key
toNoFileKey k = Key $ Q (k, emptyFilePath)
newtype Q k = Q (k, NormalizedFilePath)
deriving newtype (Eq, Hashable, NFData)
instance Show k => Show (Q k) where
show (Q (k, file)) = show k ++ "; " ++ fromNormalizedFilePath file
-- | Invariant: the 'v' must be in normal form (fully evaluated).
-- Otherwise we keep repeatedly 'rnf'ing values taken from the Shake database
newtype A v = A (Value v)
deriving Show
instance NFData (A v) where rnf (A v) = v `seq` ()
-- In the Shake database we only store one type of key/result pairs,
-- namely Q (question) / A (answer).
type instance RuleResult (Q k) = A (RuleResult k)
toShakeValue :: (BS.ByteString -> ShakeValue) -> Maybe BS.ByteString -> ShakeValue
toShakeValue = maybe ShakeNoCutoff
data ShakeValue
= -- | This is what we use when we get Nothing from
-- a rule.
ShakeNoCutoff
| -- | This is used both for `Failed`
-- as well as `Succeeded`.
ShakeResult !BS.ByteString
| ShakeStale !BS.ByteString
deriving (Generic, Show)
instance NFData ShakeValue
encodeShakeValue :: ShakeValue -> BS.ByteString
encodeShakeValue = \case
ShakeNoCutoff -> BS.empty
ShakeResult r -> BS.cons 'r' r
ShakeStale r -> BS.cons 's' r
decodeShakeValue :: BS.ByteString -> ShakeValue
decodeShakeValue bs = case BS.uncons bs of
Nothing -> ShakeNoCutoff
Just (x, xs)
| x == 'r' -> ShakeResult xs
| x == 's' -> ShakeStale xs
| otherwise -> error $ "Failed to parse shake value " <> show bs