packages feed

hls-graph 2.8.0.0 → 2.9.0.0

raw patch · 7 files changed

+165/−63 lines, 7 filesdep ~tasty-hspecPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: tasty-hspec

API changes (from Hackage documentation)

- Development.IDE.Graph.Internal.Types: ChangedStore :: RunChanged
- Development.IDE.Graph.Internal.Types: instance Control.DeepSeq.NFData value => Control.DeepSeq.NFData (Development.IDE.Graph.Internal.Types.RunResult value)
- Development.IDE.Graph.Rule: ChangedStore :: RunChanged
+ Development.IDE.Graph.Internal.Database: compute :: Database -> Stack -> Key -> RunMode -> Maybe Result -> IO Result
+ Development.IDE.Graph.Internal.Types: [runHook] :: RunResult value -> STM ()
+ Development.IDE.Graph.Internal.Types: instance GHC.Show.Show Development.IDE.Graph.Internal.Types.Step
+ Development.IDE.Graph.Internal.Types: waitForDatabaseRunningKeys :: Database -> IO ()
+ Development.IDE.Graph.Internal.Types: waitForDatabaseRunningKeysAction :: Action ()
+ Development.IDE.Graph.Internal.Types: waitRunning :: Status -> IO ()
+ Development.IDE.Graph.Rule: [runHook] :: RunResult value -> STM ()
- Development.IDE.Graph.Internal.Types: RunResult :: RunChanged -> ByteString -> value -> RunResult value
+ Development.IDE.Graph.Internal.Types: RunResult :: RunChanged -> ByteString -> value -> STM () -> RunResult value
- Development.IDE.Graph.Rule: RunResult :: RunChanged -> ByteString -> value -> RunResult value
+ Development.IDE.Graph.Rule: RunResult :: RunChanged -> ByteString -> value -> STM () -> RunResult value

Files

hls-graph.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.4 name:               hls-graph-version:            2.8.0.0+version:            2.9.0.0 synopsis:           Haskell Language Server internal graph API description:   Please see the README on GitHub at <https://github.com/haskell/haskell-language-server/tree/master/hls-graph#readme>@@ -136,7 +136,7 @@     , stm     , stm-containers     , tasty-    , tasty-hspec+    , tasty-hspec >= 1.2     , tasty-rerun    build-tool-depends: hspec-discover:hspec-discover
src/Development/IDE/Graph/Internal/Database.hs view
@@ -2,12 +2,13 @@ -- has the constraints we need on it when we get it out. {-# OPTIONS_GHC -Wno-redundant-constraints #-} +{-# LANGUAGE CPP                #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE LambdaCase         #-} {-# LANGUAGE RecordWildCards    #-} {-# LANGUAGE TypeFamilies       #-} -module Development.IDE.Graph.Internal.Database (newDatabase, incDatabase, build, getDirtySet, getKeysAndVisitAge) where+module Development.IDE.Graph.Internal.Database (compute, newDatabase, incDatabase, build, getDirtySet, getKeysAndVisitAge) where  import           Prelude                              hiding (unzip) @@ -27,7 +28,6 @@ import           Data.Either import           Data.Foldable                        (for_, traverse_) import           Data.IORef.Extra-import           Data.List.NonEmpty                   (unzip) import           Data.Maybe import           Data.Traversable                     (for) import           Data.Tuple.Extra@@ -42,7 +42,13 @@ import           System.IO.Unsafe import           System.Time.Extra                    (duration, sleep) +#if MIN_VERSION_base(4,19,0)+import           Data.Functor                         (unzip)+#else+import           Data.List.NonEmpty                   (unzip)+#endif + newDatabase :: Dynamic -> TheRules -> IO Database newDatabase databaseExtra databaseRules = do     databaseStep <- newTVarIO $ Step 0@@ -133,6 +139,9 @@                 waitAll                 pure results ++-- | isDirty+-- only dirty when it's build time is older than the changed time of one of its dependencies isDirty :: Foldable t => Result -> t (a, Result) -> Bool isDirty me = any (\(_,dep) -> resultBuilt me < resultChanged dep) @@ -143,31 +152,31 @@ -- * If no dirty dependencies and we have evaluated the key previously, then we refresh it in the current thread. --   This assumes that the implementation will be a lookup -- * Otherwise, we spawn a new thread to refresh the dirty deps (if any) and the key itself-refreshDeps :: KeySet -> Database -> Stack -> Key -> Result -> [KeySet] -> AIO (IO Result)+refreshDeps :: KeySet -> Database -> Stack -> Key -> Result -> [KeySet] -> AIO Result refreshDeps visited db stack key result = \case     -- no more deps to refresh-    [] -> pure $ compute db stack key RunDependenciesSame (Just result)+    [] -> liftIO $ compute db stack key RunDependenciesSame (Just result)     (dep:deps) -> do         let newVisited = dep <> visited         res <- builder db stack (toListKeySet (dep `differenceKeySet` visited))         case res of             Left res ->  if isDirty result res                 -- restart the computation if any of the deps are dirty-                then asyncWithCleanUp $ liftIO $ compute db stack key RunDependenciesChanged (Just result)+                then liftIO $ compute db stack key RunDependenciesChanged (Just result)                 -- else kick the rest of the deps                 else refreshDeps newVisited db stack key result deps-            Right iores -> asyncWithCleanUp $ liftIO $ do-                res <- iores+            Right iores -> do+                res <- liftIO iores                 if isDirty result res-                    then compute db stack key RunDependenciesChanged (Just result)-                    else join $ runAIO $ refreshDeps newVisited db stack key result deps+                    then liftIO $ compute db stack key RunDependenciesChanged (Just result)+                    else refreshDeps newVisited db stack key result deps  -- | Refresh a key: refresh :: Database -> Stack -> Key -> Maybe Result -> AIO (IO Result) -- refresh _ st k _ | traceShow ("refresh", st, k) False = undefined refresh db stack key result = case (addStack key stack, result) of     (Left e, _) -> throw e-    (Right stack, Just me@Result{resultDeps = ResultDeps deps}) -> refreshDeps mempty db stack key me (reverse deps)+    (Right stack, Just me@Result{resultDeps = ResultDeps deps}) -> asyncWithCleanUp $ refreshDeps mempty db stack key me (reverse deps)     (Right stack, _) ->         asyncWithCleanUp $ liftIO $ compute db stack key RunDependenciesChanged result @@ -179,14 +188,22 @@     deps <- newIORef UnknownDeps     (execution, RunResult{..}) <-         duration $ runReaderT (fromAction act) $ SAction db deps stack-    built <- readTVarIO databaseStep+    curStep <- readTVarIO databaseStep     deps <- readIORef deps-    let changed = if runChanged == ChangedRecomputeDiff then built else maybe built resultChanged result-        built' = if runChanged /= ChangedNothing then built else changed-        -- only update the deps when the rule ran with changes+    let lastChanged = maybe curStep resultChanged result+    let lastBuild = maybe curStep resultBuilt result+    -- changed time is always older than or equal to build time+    let (changed, built) =  case runChanged of+            -- some thing changed+            ChangedRecomputeDiff -> (curStep, curStep)+            -- recomputed is the same+            ChangedRecomputeSame -> (lastChanged, curStep)+            -- nothing changed+            ChangedNothing       -> (lastChanged, lastBuild)+    let -- only update the deps when the rule ran with changes         actualDeps = if runChanged /= ChangedNothing then deps else previousDeps         previousDeps= maybe UnknownDeps resultDeps result-    let res = Result runValue built' changed built actualDeps execution runStore+    let res = Result runValue built changed curStep actualDeps execution runStore     case getResultDepsDefault mempty actualDeps of         deps | not (nullKeySet deps)             && runChanged /= ChangedNothing@@ -200,7 +217,9 @@                     (getResultDepsDefault mempty previousDeps)                     deps         _ -> pure ()-    atomicallyNamed "compute" $ SMap.focus (updateStatus $ Clean res) key databaseValues+    atomicallyNamed "compute and run hook" $ do+        runHook+        SMap.focus (updateStatus $ Clean res) key databaseValues     pure res  updateStatus :: Monad m => Status -> Focus.Focus KeyDetails m ()
src/Development/IDE/Graph/Internal/Profile.hs view
@@ -13,7 +13,7 @@ import           Data.Char import           Data.Dynamic                            (toDyn) import qualified Data.HashMap.Strict                     as Map-import           Data.List                               (dropWhileEnd, foldl',+import           Data.List                               (dropWhileEnd,                                                           intercalate,                                                           partition, sort,                                                           sortBy)@@ -32,6 +32,10 @@ import           System.FilePath import           System.IO.Unsafe                        (unsafePerformIO) import           System.Time.Extra                       (Seconds)++#if !MIN_VERSION_base(4,20,0)+import           Data.List                               (foldl')+#endif  #ifdef FILE_EMBED import           Data.FileEmbed
src/Development/IDE/Graph/Internal/Types.hs view
@@ -5,6 +5,8 @@  module Development.IDE.Graph.Internal.Types where +import           Control.Concurrent.STM             (STM)+import           Control.Monad                      ((>=>)) import           Control.Monad.Catch import           Control.Monad.IO.Class import           Control.Monad.Trans.Reader@@ -77,13 +79,17 @@ getDatabase :: Action Database getDatabase = Action $ asks actionDatabase +-- | waitForDatabaseRunningKeysAction waits for all keys in the database to finish running.+waitForDatabaseRunningKeysAction :: Action ()+waitForDatabaseRunningKeysAction = getDatabase >>= liftIO . waitForDatabaseRunningKeys+ --------------------------------------------------------------------- -- DATABASE  data ShakeDatabase = ShakeDatabase !Int [Action ()] Database  newtype Step = Step Int-    deriving newtype (Eq,Ord,Hashable)+    deriving newtype (Eq,Ord,Hashable,Show)  --------------------------------------------------------------------- -- Keys@@ -109,6 +115,9 @@     databaseValues :: !(Map Key KeyDetails)     } +waitForDatabaseRunningKeys :: Database -> IO ()+waitForDatabaseRunningKeys = getDatabaseValues >=> mapM_ (waitRunning . snd)+ getDatabaseValues :: Database -> IO [(Key, Status)] getDatabaseValues = atomically                   . (fmap.fmap) (second keyStatus)@@ -135,6 +144,10 @@ getResult (Dirty m_re)         = m_re getResult (Running _ _ _ m_re) = m_re -- watch out: this returns the previous result +waitRunning :: Status -> IO ()+waitRunning Running{..} = runningWait+waitRunning _           = return ()+ data Result = Result {     resultValue     :: !Value,     resultBuilt     :: !Step, -- ^ the step when it was last recomputed@@ -186,7 +199,6 @@ -- | How the output of a rule has changed. data RunChanged     = ChangedNothing -- ^ Nothing has changed.-    | ChangedStore -- ^ The stored value has changed, but in a way that should be considered identical (used rarely).     | ChangedRecomputeSame -- ^ I recomputed the value and it was the same.     | ChangedRecomputeDiff -- ^ I recomputed the value and it was different.       deriving (Eq,Show,Generic)@@ -202,10 +214,10 @@         -- ^ The value to store in the Shake database.     ,runValue   :: value         -- ^ The value to return from 'Development.Shake.Rule.apply'.+    ,runHook    :: STM ()+        -- ^ The hook to run at the end of the build in the same transaction+        -- when the key is marked as clean.     } deriving Functor--instance NFData value => NFData (RunResult value) where-    rnf (RunResult x1 x2 x3) = rnf x1 `seq` x2 `seq` rnf x3  --------------------------------------------------------------------- -- EXCEPTIONS
test/ActionSpec.hs view
@@ -3,11 +3,14 @@  module ActionSpec where +import           Control.Concurrent                      (MVar, readMVar) import qualified Control.Concurrent                      as C import           Control.Concurrent.STM+import           Control.Monad.IO.Class                  (MonadIO (..)) import           Development.IDE.Graph                   (shakeOptions) import           Development.IDE.Graph.Database          (shakeNewDatabase,-                                                          shakeRunDatabase)+                                                          shakeRunDatabase,+                                                          shakeRunDatabaseForKeys) import           Development.IDE.Graph.Internal.Database (build, incDatabase) import           Development.IDE.Graph.Internal.Key import           Development.IDE.Graph.Internal.Types@@ -16,15 +19,50 @@ import qualified StmContainers.Map                       as STM import           Test.Hspec ++ spec :: Spec spec = do+  describe "apply1" $ it "Test build update, Buggy dirty mechanism in hls-graph #4237" $ do+    let ruleStep1 :: MVar Int -> Rules ()+        ruleStep1 m = addRule $ \CountRule _old mode -> do+            -- depends on ruleSubBranch, it always changed if dirty+            _ :: Int <- apply1 SubBranchRule+            let r = 1+            case mode of+                -- it update the built step+                RunDependenciesChanged -> do+                    _ <- liftIO $ C.modifyMVar m $ \x -> return (x+1, x)+                    return $ RunResult ChangedRecomputeSame "" r (return ())+                -- this won't update the built step+                RunDependenciesSame ->+                    return $ RunResult ChangedNothing "" r (return ())+    count <- C.newMVar 0+    count1 <- C.newMVar 0+    db <- shakeNewDatabase shakeOptions $ do+      ruleSubBranch count+      ruleStep1 count1+    -- bootstrapping the database+    _ <- shakeRunDatabase db $ pure $ apply1 CountRule -- count = 1+    let child = newKey SubBranchRule+    let parent = newKey CountRule+    -- instruct to RunDependenciesChanged then CountRule should be recomputed+    -- result should be changed 0, build 1+    _res1 <- shakeRunDatabaseForKeys (Just [child]) db [apply1 CountRule] -- count = 2+    -- since child changed = parent build+    -- instruct to RunDependenciesSame then CountRule should not be recomputed+    -- result should be changed 0, build 1+    _res3 <- shakeRunDatabaseForKeys (Just [parent]) db [apply1 CountRule] -- count = 2+    -- invariant child changed = parent build should remains after RunDependenciesSame+    -- this used to be a bug, with additional computation, see https://github.com/haskell/haskell-language-server/pull/4238+    _res3 <- shakeRunDatabaseForKeys (Just [parent]) db [apply1 CountRule] -- count = 2+    c1 <- readMVar count1+    c1 `shouldBe` 2   describe "apply1" $ do     it "computes a rule with no dependencies" $ do-      db <- shakeNewDatabase shakeOptions $ do-        ruleUnit+      db <- shakeNewDatabase shakeOptions ruleUnit       res <- shakeRunDatabase db $-        pure $ do-          apply1 (Rule @())+        pure $ apply1 (Rule @())       res `shouldBe` [()]     it "computes a rule with one dependency" $ do       db <- shakeNewDatabase shakeOptions $ do@@ -38,8 +76,7 @@         ruleBool       let theKey = Rule @Bool       res <- shakeRunDatabase db $-        pure $ do-          apply1 theKey+        pure $ apply1 theKey       res `shouldBe` [True]       Just (Clean res) <- lookup (newKey theKey) <$> getDatabaseValues theDb       resultDeps res `shouldBe` ResultDeps [singletonKeySet $ newKey (Rule @())]@@ -49,14 +86,12 @@         ruleBool       let theKey = Rule @Bool       res <- shakeRunDatabase db $-        pure $ do-          apply1 theKey+        pure $ apply1 theKey       res `shouldBe` [True]       Just KeyDetails {..} <- atomically $ STM.lookup (newKey (Rule @())) databaseValues-      keyReverseDeps `shouldBe` (singletonKeySet $ newKey theKey)+      keyReverseDeps `shouldBe` singletonKeySet (newKey theKey)     it "rethrows exceptions" $ do-      db <- shakeNewDatabase shakeOptions $ do-        addRule $ \(Rule :: Rule ()) _old _mode -> error "boom"+      db <- shakeNewDatabase shakeOptions $ addRule $ \(Rule :: Rule ()) _old _mode -> error "boom"       let res = shakeRunDatabase db $ pure $ apply1 (Rule @())       res `shouldThrow` anyErrorCall     it "computes a rule with branching dependencies does not invoke phantom dependencies #3423" $ do@@ -81,18 +116,16 @@       countRes <- build theDb emptyStack [SubBranchRule]       snd countRes `shouldBe` [1 :: Int] -  describe "applyWithoutDependency" $ do-    it "does not track dependencies" $ do-      db@(ShakeDatabase _ _ theDb) <- shakeNewDatabase shakeOptions $ do-        ruleUnit-        addRule $ \Rule _old _mode -> do-            [()] <- applyWithoutDependency [Rule]-            return $ RunResult ChangedRecomputeDiff "" True+  describe "applyWithoutDependency" $ it "does not track dependencies" $ do+    db@(ShakeDatabase _ _ theDb) <- shakeNewDatabase shakeOptions $ do+      ruleUnit+      addRule $ \Rule _old _mode -> do+          [()] <- applyWithoutDependency [Rule]+          return $ RunResult ChangedRecomputeDiff "" True $ return () -      let theKey = Rule @Bool-      res <- shakeRunDatabase db $-        pure $ do-          applyWithoutDependency [theKey]-      res `shouldBe` [[True]]-      Just (Clean res) <- lookup (newKey theKey) <$> getDatabaseValues theDb-      resultDeps res `shouldBe` UnknownDeps+    let theKey = Rule @Bool+    res <- shakeRunDatabase db $+      pure $ applyWithoutDependency [theKey]+    res `shouldBe` [[True]]+    Just (Clean res) <- lookup (newKey theKey) <$> getDatabaseValues theDb+    resultDeps res `shouldBe` UnknownDeps
test/DatabaseSpec.hs view
@@ -2,16 +2,18 @@  module DatabaseSpec where -import           Development.IDE.Graph                 (shakeOptions)-import           Development.IDE.Graph.Database        (shakeNewDatabase,-                                                        shakeRunDatabase)-import           Development.IDE.Graph.Internal.Action (apply1)-import           Development.IDE.Graph.Internal.Rules  (addRule)+import           Development.IDE.Graph                   (newKey, shakeOptions)+import           Development.IDE.Graph.Database          (shakeNewDatabase,+                                                          shakeRunDatabase)+import           Development.IDE.Graph.Internal.Action   (apply1)+import           Development.IDE.Graph.Internal.Database (compute, incDatabase)+import           Development.IDE.Graph.Internal.Rules    (addRule) import           Development.IDE.Graph.Internal.Types import           Example-import           System.Time.Extra                     (timeout)+import           System.Time.Extra                       (timeout) import           Test.Hspec + spec :: Spec spec = do     describe "Evaluation" $ do@@ -20,6 +22,28 @@                 ruleBool                 addRule $ \Rule _old _mode -> do                     True <- apply1 (Rule @Bool)-                    return $ RunResult ChangedRecomputeDiff "" ()+                    return $ RunResult ChangedRecomputeDiff "" () (return ())             let res = shakeRunDatabase db $ pure $ apply1 (Rule @())             timeout 1 res `shouldThrow` \StackException{} -> True++    describe "compute" $ do+      it "build step and changed step updated correctly" $ do+        (ShakeDatabase _ _ theDb) <- shakeNewDatabase shakeOptions $ do+          ruleStep++        let k = newKey $ Rule @()+        -- ChangedRecomputeSame+        r1@Result{resultChanged=rc1, resultBuilt=rb1} <- compute theDb emptyStack k RunDependenciesChanged Nothing+        incDatabase theDb Nothing+        -- ChangedRecomputeSame+        r2@Result{resultChanged=rc2, resultBuilt=rb2} <- compute theDb emptyStack k RunDependenciesChanged (Just r1)+        incDatabase theDb Nothing+        -- changed Nothing+        Result{resultChanged=rc3, resultBuilt=rb3} <- compute theDb emptyStack k RunDependenciesSame (Just r2)+        rc1 `shouldBe` Step 0+        rc2 `shouldBe` Step 0+        rc3 `shouldBe` Step 0++        rb1 `shouldBe` Step 0+        rb2 `shouldBe` Step 1+        rb3 `shouldBe` Step 1
test/Example.hs view
@@ -20,15 +20,21 @@  type instance RuleResult (Rule a) = a +ruleStep :: Rules ()+ruleStep = addRule $ \(Rule :: Rule ()) _old mode -> do+    case mode of+        RunDependenciesChanged -> return $ RunResult ChangedRecomputeSame "" () (return ())+        RunDependenciesSame -> return $ RunResult ChangedNothing "" () (return ())+ ruleUnit :: Rules () ruleUnit = addRule $ \(Rule :: Rule ()) _old _mode -> do-    return $ RunResult ChangedRecomputeDiff "" ()+    return $ RunResult ChangedRecomputeDiff "" () (return ())  -- | Depends on Rule @() ruleBool :: Rules () ruleBool = addRule $ \Rule _old _mode -> do     () <- apply1 Rule-    return $ RunResult ChangedRecomputeDiff "" True+    return $ RunResult ChangedRecomputeDiff "" True (return ())   data CondRule = CondRule@@ -39,7 +45,7 @@ ruleCond :: C.MVar Bool -> Rules () ruleCond mv = addRule $ \CondRule _old _mode -> do     r <- liftIO $ C.modifyMVar mv $ \x -> return (not x, x)-    return $ RunResult ChangedRecomputeDiff "" r+    return $ RunResult ChangedRecomputeDiff "" r (return ())  data BranchedRule = BranchedRule     deriving (Eq, Generic, Hashable, NFData, Show, Typeable)@@ -50,9 +56,9 @@     r <- apply1 CondRule     if r then do             _ <- apply1 SubBranchRule-            return $ RunResult ChangedRecomputeDiff "" (1 :: Int)+            return $ RunResult ChangedRecomputeDiff "" (1 :: Int) (return ())          else-            return $ RunResult ChangedRecomputeDiff "" (2 :: Int)+            return $ RunResult ChangedRecomputeDiff "" (2 :: Int) (return ())  data SubBranchRule = SubBranchRule     deriving (Eq, Generic, Hashable, NFData, Show, Typeable)@@ -61,4 +67,8 @@ ruleSubBranch :: C.MVar Int -> Rules () ruleSubBranch mv = addRule $ \SubBranchRule _old _mode -> do     r <- liftIO $ C.modifyMVar mv $ \x -> return (x+1, x)-    return $ RunResult ChangedRecomputeDiff "" r+    return $ RunResult ChangedRecomputeDiff "" r (return ())++data CountRule = CountRule+    deriving (Eq, Generic, Hashable, NFData, Show, Typeable)+type instance RuleResult CountRule = Int