liquidhaskell-0.8.10.1: src/Language/Haskell/Liquid/GHC/Plugin/Util.hs
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveDataTypeable #-}
module Language.Haskell.Liquid.GHC.Plugin.Util (
partitionMaybe
, extractSpecComments
-- * Serialising and deserialising things from/to specs.
, serialiseLiquidLib
, deserialiseLiquidLib
-- * Aborting the plugin execution
, pluginAbort
) where
import GhcPlugins as GHC
import UniqDFM
import IfaceSyn
import Panic ( throwGhcExceptionIO, GhcException(..) )
import Data.Foldable ( asum )
import Control.Monad.IO.Class
import Control.Monad
import qualified Data.Binary as B
import Data.Binary ( Binary )
import qualified Data.ByteString.Lazy as B
import Data.Typeable
import Data.Maybe ( listToMaybe )
import Data.Data
import Data.Either ( partitionEithers )
import Language.Haskell.Liquid.GHC.Plugin.Types ( SpecComment
, LiquidLib
)
pluginAbort :: MonadIO m => String -> m a
pluginAbort = liftIO . throwGhcExceptionIO . ProgramError
-- | Courtesy of [inspection testing](https://github.com/nomeata/inspection-testing/blob/master/src/Test/Inspection/Plugin.hs)
partitionMaybe :: (a -> Maybe b) -> [a] -> ([a], [b])
partitionMaybe f = partitionEithers . map (\x -> maybe (Left x) Right (f x))
-- | Extracts the spec comments from the Core 'Annotation's. It returns a
-- "cleaned" 'ModGuts' which doesn't contain the deserialised 'Annotation's.
-- This also means that these 'Annotation's /won't/ land into an interface file,
-- and we won't be able to retrieve them back later on.
extractSpecComments :: ModGuts -> (ModGuts, [SpecComment])
extractSpecComments = extractModuleAnnotations
-- | Tries to deserialise the 'Annotation's in the input 'ModGuts', pruning the latter
-- upon successful deserialisation.
extractModuleAnnotations :: forall a. (Typeable a, Data a) => ModGuts -> (ModGuts, [a])
extractModuleAnnotations guts = (guts', extracted)
where
thisModule = mg_module guts
(anns_clean, extracted) = partitionMaybe tryDeserialise (mg_anns guts)
guts' = guts { mg_anns = anns_clean }
tryDeserialise :: Annotation -> Maybe a
tryDeserialise (Annotation (ModuleTarget m) payload)
| thisModule == m = fromSerialized deserializeWithData payload
| otherwise = Nothing
tryDeserialise (Annotation (NamedTarget _) payload) --NOTE(adn) What is the correct behaviour here?
| Just a <- fromSerialized deserializeWithData payload
= Just a
tryDeserialise _
= Nothing
--
-- Serialising and deserialising Specs
--
deserialiseBinaryObject :: forall a. (Typeable a, Binary a)
=> Module
-> ExternalPackageState
-> HomePackageTable
-> Maybe a
deserialiseBinaryObject thisModule eps hpt = asum [extractFromHpt, extractFromEps]
where
extractFromEps :: Maybe a
extractFromEps = listToMaybe $ findAnns deserialise (eps_ann_env eps) (ModuleTarget thisModule)
extractFromHpt :: Maybe a
extractFromHpt = do
modInfo <- lookupUDFM hpt (moduleName thisModule)
guard (thisModule == (mi_module . hm_iface $ modInfo))
xs <- mapM (fromSerialized deserialise . ifAnnotatedValue) (mi_anns . hm_iface $ modInfo)
listToMaybe xs
deserialise :: [B.Word8] -> a
deserialise payload = B.decode (B.pack payload)
serialiseBinaryObject :: forall a. (Binary a, Typeable a) => a -> Module -> Annotation
serialiseBinaryObject obj thisModule = serialised
where
serialised :: Annotation
serialised = Annotation (ModuleTarget thisModule) (toSerialized (B.unpack . B.encode) obj)
-- | Serialise a 'LiquidLib', removing the termination checks from the target.
serialiseLiquidLib :: LiquidLib -> Module -> Annotation
serialiseLiquidLib lib = serialiseBinaryObject @LiquidLib lib
deserialiseLiquidLib :: Module -> ExternalPackageState -> HomePackageTable -> Maybe LiquidLib
deserialiseLiquidLib thisModule = deserialiseBinaryObject @LiquidLib thisModule