{-# LANGUAGE DerivingVia #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE RankNTypes #-} module Development.IDE.Core.UseStale ( Age(..) , Tracked , unTrack , PositionMap , TrackedStale (..) , untrackedStaleValue , unsafeMkStale , unsafeMkCurrent , unsafeCopyAge , MapAge (..) , dualPositionMap , useWithStale , useWithStale_ ) where import Control.Arrow import Control.Category (Category) import qualified Control.Category as C import Control.DeepSeq (NFData) import Data.Aeson import Data.Coerce (coerce) import Data.Functor ((<&>)) import Data.Functor.Identity (Identity (Identity)) import Data.Kind (Type) import Data.String (fromString) import Development.IDE (Action, IdeRule, NormalizedFilePath, Range, rangeToRealSrcSpan, realSrcSpanToRange) import qualified Development.IDE.Core.PositionMapping as P import qualified Development.IDE.Core.Shake as IDE import Development.IDE.GHC.Compat (RealSrcSpan, srcSpanFile) import Development.IDE.GHC.Compat.Util (unpackFS) ------------------------------------------------------------------------------ -- | A data kind for 'Tracked'. data Age = Current | Stale Type ------------------------------------------------------------------------------ -- | Some value, tagged with its age. All 'Current' ages are considered to be -- the same thing, but 'Stale' values are protected by an untouchable variable -- to ensure they can't be unified. newtype Tracked (age :: Age) a = UnsafeTracked { unTrack :: a } deriving stock (Functor, Foldable, Traversable) deriving newtype (Eq, Ord, Show, Read, ToJSON, FromJSON, NFData) deriving (Applicative, Monad) via Identity ------------------------------------------------------------------------------ -- | Like 'P.PositionMapping', but with annotated ages for how 'Tracked' values -- change. Use the 'Category' instance to compose 'PositionMapping's in order -- to transform between values of different stale ages. newtype PositionMap (from :: Age) (to :: Age) = PositionMap { _getPositionMapping :: P.PositionMapping } instance Category PositionMap where id = coerce P.zeroMapping (.) = coerce P.composeDelta ------------------------------------------------------------------------------ -- | Get a 'PositionMap' that runs in the opposite direction. dualPositionMap :: PositionMap from to -> PositionMap to from dualPositionMap (PositionMap (P.PositionMapping (P.PositionDelta from to))) = PositionMap $ P.PositionMapping $ P.PositionDelta to from ------------------------------------------------------------------------------ -- | A pair containing a @'Tracked' 'Stale'@ value, as well as -- a 'PositionMapping' that will fast-forward values to the current age. data TrackedStale a where TrackedStale :: Tracked (Stale s) a -> PositionMap (Stale s) Current -> TrackedStale a instance Functor TrackedStale where fmap f (TrackedStale t pm) = TrackedStale (fmap f t) pm untrackedStaleValue :: TrackedStale a -> a untrackedStaleValue (TrackedStale ta _) = coerce ta ------------------------------------------------------------------------------ -- | A class for which 'Tracked' values can be run across a 'PositionMapping' -- to change their ages. class MapAge a where {-# MINIMAL mapAgeFrom | mapAgeTo #-} mapAgeFrom :: PositionMap from to -> Tracked to a -> Maybe (Tracked from a) mapAgeFrom = mapAgeTo . dualPositionMap mapAgeTo :: PositionMap from to -> Tracked from a -> Maybe (Tracked to a) mapAgeTo = mapAgeFrom . dualPositionMap instance MapAge Range where mapAgeFrom = coerce P.fromCurrentRange mapAgeTo = coerce P.toCurrentRange instance MapAge RealSrcSpan where mapAgeFrom = invMapAge (\fs -> rangeToRealSrcSpan (fromString $ unpackFS fs)) (srcSpanFile &&& realSrcSpanToRange) . mapAgeFrom ------------------------------------------------------------------------------ -- | Helper function for deriving 'MapAge' for values in terms of other -- instances. invMapAge :: (c -> a -> b) -> (b -> (c, a)) -> (Tracked from a -> Maybe (Tracked to a)) -> Tracked from b -> Maybe (Tracked to b) invMapAge to from f t = let (c, t') = unTrack $ fmap from t in fmap (fmap $ to c) $ f $ UnsafeTracked t' unsafeMkCurrent :: age -> Tracked 'Current age unsafeMkCurrent = coerce unsafeMkStale :: age -> Tracked (Stale s) age unsafeMkStale = coerce unsafeCopyAge :: Tracked age a -> b -> Tracked age b unsafeCopyAge _ = coerce -- | Request a Rule result, it not available return the last computed result, if any, which may be stale useWithStale :: IdeRule k v => k -> NormalizedFilePath -> Action (Maybe (TrackedStale v)) useWithStale key file = do x <- IDE.useWithStale key file pure $ x <&> \(v, pm) -> TrackedStale (coerce v) (coerce pm) -- | Request a Rule result, it not available return the last computed result which may be stale. -- Errors out if none available. useWithStale_ :: IdeRule k v => k -> NormalizedFilePath -> Action (TrackedStale v) useWithStale_ key file = do (v, pm) <- IDE.useWithStale_ key file pure $ TrackedStale (coerce v) (coerce pm)