From 050586d59e80fbbfd7b1aec2319cebcc3a3da251 Mon Sep 17 00:00:00 2001 From: Elodie Lander Date: Wed, 4 Sep 2024 06:48:42 -0500 Subject: [PATCH 01/14] WIP Add input rule constraints --- ghcide/ghcide.cabal | 1 + ghcide/src/Development/IDE/Core/InputPath.hs | 7 ++ ghcide/src/Development/IDE/Core/Shake.hs | 113 ++++++++++-------- hls-graph/hls-graph.cabal | 1 + .../IDE/Graph/Internal/RuleInput.hs | 17 +++ 5 files changed, 86 insertions(+), 53 deletions(-) create mode 100644 ghcide/src/Development/IDE/Core/InputPath.hs create mode 100644 hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs diff --git a/ghcide/ghcide.cabal b/ghcide/ghcide.cabal index bf88a55ed3..9c93aecc08 100644 --- a/ghcide/ghcide.cabal +++ b/ghcide/ghcide.cabal @@ -135,6 +135,7 @@ library Development.IDE.Core.FileStore Development.IDE.Core.FileUtils Development.IDE.Core.IdeConfiguration + Development.IDE.Core.InputPath Development.IDE.Core.OfInterest Development.IDE.Core.PluginUtils Development.IDE.Core.PositionMapping diff --git a/ghcide/src/Development/IDE/Core/InputPath.hs b/ghcide/src/Development/IDE/Core/InputPath.hs new file mode 100644 index 0000000000..7148ec682b --- /dev/null +++ b/ghcide/src/Development/IDE/Core/InputPath.hs @@ -0,0 +1,7 @@ +module Development.IDE.Core.InputPath where + +import Development.IDE.Graph.Internal.RuleInput (Input) +import Development.IDE (NormalizedFilePath) + +newtype InputPath (i :: Input) = + InputPath { unInputPath :: NormalizedFilePath } \ No newline at end of file diff --git a/ghcide/src/Development/IDE/Core/Shake.hs b/ghcide/src/Development/IDE/Core/Shake.hs index e37c3741c7..120d8a0515 100644 --- a/ghcide/src/Development/IDE/Core/Shake.hs +++ b/ghcide/src/Development/IDE/Core/Shake.hs @@ -1,6 +1,7 @@ -- Copyright (c) 2019 The DAML Authors. All rights reserved. -- SPDX-License-Identifier: Apache-2.0 +{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DuplicateRecordFields #-} @@ -121,6 +122,7 @@ import Data.Vector (Vector) import qualified Data.Vector as Vector import Development.IDE.Core.Debouncer import Development.IDE.Core.FileUtils (getModTime) +import Development.IDE.Core.InputPath (InputPath (unInputPath, InputPath)) import Development.IDE.Core.PositionMapping import Development.IDE.Core.ProgressReporting import Development.IDE.Core.RuleTypes @@ -179,6 +181,7 @@ import System.FilePath hiding (makeRelative) import System.IO.Unsafe (unsafePerformIO) import System.Time.Extra import UnliftIO (MonadUnliftIO (withRunInIO)) +import Development.IDE.Graph.Internal.RuleInput (RuleInput, HasInput) data Log @@ -342,7 +345,7 @@ type WithProgressFunc = forall a. type WithIndefiniteProgressFunc = forall a. T.Text -> LSP.ProgressCancellable -> IO a -> IO a -type GetStalePersistent = NormalizedFilePath -> IdeAction (Maybe (Dynamic,PositionDelta,Maybe Int32)) +type GetStalePersistent = InputPath i -> IdeAction (Maybe (Dynamic,PositionDelta,Maybe Int32)) getShakeExtras :: Action ShakeExtras getShakeExtras = do @@ -384,7 +387,7 @@ getPluginConfigAction plId = do -- This is called when we don't already have a result, or computing the rule failed. -- The result of this function will always be marked as 'stale', and a 'proper' rebuild of the rule will -- be queued if the rule hasn't run before. -addPersistentRule :: IdeRule k v => k -> (NormalizedFilePath -> IdeAction (Maybe (v,PositionDelta,Maybe Int32))) -> Rules () +addPersistentRule :: IdeRule k i is v => k -> (InputPath i -> IdeAction (Maybe (v,PositionDelta,Maybe Int32))) -> Rules () addPersistentRule k getVal = do ShakeExtras{persistentKeys} <- getShakeExtrasRules void $ liftIO $ atomically $ modifyTVar' persistentKeys $ insertKeyMap (newKey k) (fmap (fmap (first3 toDyn)) . getVal) @@ -452,7 +455,7 @@ getIdeOptionsIO ide = do -- | Return the most recent, potentially stale, value and a PositionMapping -- for the version of that value. -lastValueIO :: IdeRule k v => ShakeExtras -> k -> NormalizedFilePath -> IO (Maybe (v, PositionMapping)) +lastValueIO :: IdeRule k i is v => ShakeExtras -> k -> InputPath i -> IO (Maybe (v, PositionMapping)) lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k file = do let readPersistent @@ -498,7 +501,7 @@ lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k file = do -- | Return the most recent, potentially stale, value and a PositionMapping -- for the version of that value. -lastValue :: IdeRule k v => k -> NormalizedFilePath -> Action (Maybe (v, PositionMapping)) +lastValue :: IdeRule k i is v => k -> InputPath i -> Action (Maybe (v, PositionMapping)) lastValue key file = do s <- getShakeExtras liftIO $ lastValueIO s key file @@ -513,9 +516,11 @@ mappingForVersion allMappings file (Just (VFSVersion ver)) = do return $ maybe zeroMapping snd $ EM.lookup ver =<< mapping mappingForVersion _ _ _ = pure zeroMapping -type IdeRule k v = +type IdeRule k i is v = ( Shake.RuleResult k ~ v , Shake.ShakeValue k + , RuleInput k ~ is + , HasInput i is , Show v , Typeable v , NFData v @@ -581,10 +586,10 @@ shakeDatabaseProfileIO mbProfileDir = do shakeProfileDatabase shakeDb $ dir file return (dir file) -setValues :: IdeRule k v +setValues :: IdeRule k i is v => Values -> k - -> NormalizedFilePath + -> InputPath i -> Value v -> Vector FileDiagnostic -> STM () @@ -607,11 +612,11 @@ deleteValue ShakeExtras{state} key file = do -- | We return Nothing if the rule has not run and Just Failed if it has failed to produce a value. getValues :: - forall k v. - IdeRule k v => + forall k i is v. + IdeRule k i is v => Values -> k -> - NormalizedFilePath -> + InputPath i -> STM (Maybe (Value v, Vector FileDiagnostic)) getValues state key file = do STM.lookup (toKey key file) state >>= \case @@ -1010,23 +1015,23 @@ preservedKeys checkParents = HSet.fromList $ -- | Define a new Rule without early cutoff define - :: IdeRule k v - => Recorder (WithPriority Log) -> (k -> NormalizedFilePath -> Action (IdeResult v)) -> Rules () + :: IdeRule k i is v + => Recorder (WithPriority Log) -> (k -> InputPath i -> Action (IdeResult v)) -> Rules () define recorder op = defineEarlyCutoff recorder $ Rule $ \k v -> (Nothing,) <$> op k v defineNoDiagnostics - :: IdeRule k v - => Recorder (WithPriority Log) -> (k -> NormalizedFilePath -> Action (Maybe v)) -> Rules () + :: IdeRule k i is v + => Recorder (WithPriority Log) -> (k -> InputPath i -> Action (Maybe v)) -> Rules () defineNoDiagnostics recorder op = defineEarlyCutoff recorder $ RuleNoDiagnostics $ \k v -> (Nothing,) <$> op k v -- | Request a Rule result if available -use :: IdeRule k v - => k -> NormalizedFilePath -> Action (Maybe v) +use :: IdeRule k i is v + => k -> InputPath i -> Action (Maybe v) use key file = runIdentity <$> uses key (Identity file) -- | 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 (v, PositionMapping)) +useWithStale :: IdeRule k i is v + => k -> InputPath i -> Action (Maybe (v, PositionMapping)) useWithStale key file = runIdentity <$> usesWithStale key (Identity file) -- |Request a Rule result, it not available return the last computed result @@ -1036,8 +1041,8 @@ useWithStale key file = runIdentity <$> usesWithStale key (Identity file) -- none available. -- -- WARNING: Not suitable for PluginHandlers. Use `useWithStaleE` instead. -useWithStale_ :: IdeRule k v - => k -> NormalizedFilePath -> Action (v, PositionMapping) +useWithStale_ :: IdeRule k i is v + => k -> InputPath i -> Action (v, PositionMapping) useWithStale_ key file = runIdentity <$> usesWithStale_ key (Identity file) -- |Plural version of 'useWithStale_' @@ -1046,7 +1051,7 @@ useWithStale_ key file = runIdentity <$> usesWithStale_ key (Identity file) -- none available. -- -- WARNING: Not suitable for PluginHandlers. -usesWithStale_ :: (Traversable f, IdeRule k v) => k -> f NormalizedFilePath -> Action (f (v, PositionMapping)) +usesWithStale_ :: (Traversable f, IdeRule k i is v) => k -> f (InputPath i) -> Action (f (v, PositionMapping)) usesWithStale_ key files = do res <- usesWithStale key files case sequence res of @@ -1077,11 +1082,11 @@ data FastResult a = FastResult { stale :: Maybe (a,PositionMapping), uptoDate :: -- | Lookup value in the database and return with the stale value immediately -- Will queue an action to refresh the value. -- Might block the first time the rule runs, but never blocks after that. -useWithStaleFast :: IdeRule k v => k -> NormalizedFilePath -> IdeAction (Maybe (v, PositionMapping)) +useWithStaleFast :: IdeRule k i is v => k -> InputPath i -> IdeAction (Maybe (v, PositionMapping)) useWithStaleFast key file = stale <$> useWithStaleFast' key file -- | Same as useWithStaleFast but lets you wait for an up to date result -useWithStaleFast' :: IdeRule k v => k -> NormalizedFilePath -> IdeAction (FastResult v) +useWithStaleFast' :: IdeRule k i is v => k -> InputPath i -> IdeAction (FastResult v) useWithStaleFast' key file = do -- This lookup directly looks up the key in the shake database and -- returns the last value that was computed for this key without @@ -1108,7 +1113,7 @@ useWithStaleFast' key file = do res <- lastValueIO s key file pure $ FastResult res waitValue -useNoFile :: IdeRule k v => k -> Action (Maybe v) +useNoFile :: IdeRule k i is v => k -> Action (Maybe v) useNoFile key = use key emptyFilePath -- Requests a rule if available. @@ -1117,10 +1122,10 @@ useNoFile key = use key emptyFilePath -- none available. -- -- WARNING: Not suitable for PluginHandlers. Use `useE` instead. -use_ :: IdeRule k v => k -> NormalizedFilePath -> Action v +use_ :: IdeRule k i is v => k -> InputPath i -> Action v use_ key file = runIdentity <$> uses_ key (Identity file) -useNoFile_ :: IdeRule k v => k -> Action v +useNoFile_ :: IdeRule k i is v => k -> Action v useNoFile_ key = use_ key emptyFilePath -- |Plural version of `use_` @@ -1129,7 +1134,7 @@ useNoFile_ key = use_ key emptyFilePath -- none available. -- -- WARNING: Not suitable for PluginHandlers. Use `usesE` instead. -uses_ :: (Traversable f, IdeRule k v) => k -> f NormalizedFilePath -> Action (f v) +uses_ :: (Traversable f, IdeRule k i is v) => k -> f (InputPath i) -> Action (f v) uses_ key files = do res <- uses key files case sequence res of @@ -1137,13 +1142,13 @@ uses_ key files = do Just v -> return v -- | Plural version of 'use' -uses :: (Traversable f, IdeRule k v) - => k -> f NormalizedFilePath -> Action (f (Maybe v)) +uses :: (Traversable f, IdeRule k i is v) + => k -> f (InputPath i) -> Action (f (Maybe v)) uses key files = fmap (\(A value) -> currentValue value) <$> apply (fmap (Q . (key,)) files) -- | Return the last computed result which might be stale. -usesWithStale :: (Traversable f, IdeRule k v) - => k -> f NormalizedFilePath -> Action (f (Maybe (v, PositionMapping))) +usesWithStale :: (Traversable f, IdeRule k i is v) + => k -> f (InputPath i) -> Action (f (Maybe (v, PositionMapping))) usesWithStale key files = do _ <- apply (fmap (Q . (key,)) files) -- We don't look at the result of the 'apply' since 'lastValue' will @@ -1151,25 +1156,25 @@ usesWithStale key files = do -- whether the rule succeeded or not. traverse (lastValue key) files -useWithoutDependency :: IdeRule k v - => k -> NormalizedFilePath -> Action (Maybe v) +useWithoutDependency :: IdeRule k i is v + => k -> InputPath i -> Action (Maybe v) useWithoutDependency key file = (\(Identity (A value)) -> currentValue value) <$> applyWithoutDependency (Identity (Q (key, file))) -data RuleBody k v - = Rule (k -> NormalizedFilePath -> Action (Maybe BS.ByteString, IdeResult v)) - | RuleNoDiagnostics (k -> NormalizedFilePath -> Action (Maybe BS.ByteString, Maybe v)) +data RuleBody k i v + = Rule (k -> InputPath i -> Action (Maybe BS.ByteString, IdeResult v)) + | RuleNoDiagnostics (k -> InputPath i -> Action (Maybe BS.ByteString, Maybe v)) | RuleWithCustomNewnessCheck { newnessCheck :: BS.ByteString -> BS.ByteString -> Bool - , build :: k -> NormalizedFilePath -> Action (Maybe BS.ByteString, Maybe v) + , build :: k -> InputPath i -> Action (Maybe BS.ByteString, Maybe v) } - | RuleWithOldValue (k -> NormalizedFilePath -> Value v -> Action (Maybe BS.ByteString, IdeResult v)) + | RuleWithOldValue (k -> InputPath i -> Value v -> Action (Maybe BS.ByteString, IdeResult v)) -- | Define a new Rule with early cutoff defineEarlyCutoff - :: IdeRule k v + :: IdeRule k i is v => Recorder (WithPriority Log) - -> RuleBody k v + -> RuleBody k i v -> Rules () defineEarlyCutoff recorder (Rule op) = addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> otTracedAction key file mode traceA $ \traceDiagnostics -> do extras <- getShakeExtras @@ -1197,32 +1202,33 @@ defineEarlyCutoff recorder (RuleWithOldValue op) = addRule $ \(Q (key, file)) (o updateFileDiagnostics recorder file ver (newKey key) extras . map (\(_,y,z) -> (y,z)) $ diags defineEarlyCutoff' diagnostics (==) key file old mode $ op key file -defineNoFile :: IdeRule k v => Recorder (WithPriority Log) -> (k -> Action v) -> Rules () +defineNoFile :: IdeRule k i is v => Recorder (WithPriority Log) -> (k -> Action v) -> Rules () defineNoFile recorder f = defineNoDiagnostics recorder $ \k file -> do if file == emptyFilePath then do res <- f k; return (Just res) else fail $ "Rule " ++ show k ++ " should always be called with the empty string for a file" -defineEarlyCutOffNoFile :: IdeRule k v => Recorder (WithPriority Log) -> (k -> Action (BS.ByteString, v)) -> Rules () -defineEarlyCutOffNoFile recorder f = defineEarlyCutoff recorder $ RuleNoDiagnostics $ \k file -> do +defineEarlyCutOffNoFile :: IdeRule k i is v => Recorder (WithPriority Log) -> (k -> Action (BS.ByteString, v)) -> Rules () +defineEarlyCutOffNoFile recorder f = defineEarlyCutoff recorder $ RuleNoDiagnostics $ \k (InputPath file) -> do if file == emptyFilePath then do (hashString, res) <- f k; return (Just hashString, Just res) else fail $ "Rule " ++ show k ++ " should always be called with the empty string for a file" defineEarlyCutoff' - :: forall k v. IdeRule k v + :: forall k i is v. IdeRule k i is v => (Maybe Int32 -> [FileDiagnostic] -> Action ()) -- ^ update diagnostics -- | compare current and previous for freshness -> (BS.ByteString -> BS.ByteString -> Bool) -> k - -> NormalizedFilePath + -> InputPath i -> Maybe BS.ByteString -> RunMode -> (Value v -> Action (Maybe BS.ByteString, IdeResult v)) -> Action (RunResult (A (RuleResult k))) defineEarlyCutoff' doDiagnostics cmp key file mbOld mode action = do + let rawFile = unInputPath file ShakeExtras{state, progress, dirtyKeys} <- getShakeExtras options <- getIdeOptions let trans g x = withRunInIO $ \run -> g (run x) - (if optSkipProgress options key then id else trans (inProgress progress file)) $ do + (if optSkipProgress options key then id else trans (inProgress progress rawFile)) $ do val <- case mbOld of Just old | mode == RunDependenciesSame -> do mbValue <- liftIO $ atomicallyNamed "define - read 1" $ getValues state key file @@ -1249,7 +1255,7 @@ defineEarlyCutoff' doDiagnostics cmp key file mbOld mode action = do (mbBs, (diags, mbRes)) <- actionCatch (do v <- action staleV; liftIO $ evaluate $ force v) $ \(e :: SomeException) -> do - pure (Nothing, ([ideErrorText file $ T.pack $ show e | not $ isBadDependency e],Nothing)) + pure (Nothing, ([ideErrorText rawFile $ T.pack $ show e | not $ isBadDependency e],Nothing)) ver <- estimateFileVersionUnsafely key mbRes file (bs, res) <- case mbRes of @@ -1270,7 +1276,7 @@ defineEarlyCutoff' doDiagnostics cmp key file mbOld mode action = do -- this hook needs to be run in the same transaction as the key is marked clean -- see Note [Housekeeping rule cache and dirty key outside of hls-graph] setValues state key file res (Vector.fromList diags) - modifyTVar' dirtyKeys (deleteKeySet $ toKey key file) + modifyTVar' dirtyKeys (deleteKeySet $ toKey key rawFile) return res where -- Highly unsafe helper to compute the version of a file @@ -1279,10 +1285,10 @@ defineEarlyCutoff' doDiagnostics cmp key file mbOld mode action = do estimateFileVersionUnsafely :: k -> Maybe v - -> NormalizedFilePath + -> InputPath i -> Action (Maybe FileVersion) estimateFileVersionUnsafely _k v fp - | fp == emptyFilePath = pure Nothing + | unInputPath fp == emptyFilePath = pure Nothing | Just Refl <- eqT @k @GetModificationTime = pure v -- GetModificationTime depends on these rules, so avoid creating a cycle | Just Refl <- eqT @k @AddWatchedFile = pure Nothing @@ -1457,9 +1463,10 @@ kickSignal testing lspEnv files msg = when testing $ liftIO $ mRunLspT lspEnv $ toJSON $ map fromNormalizedFilePath files -- | Add kick start/done signal to rule -runWithSignal :: (KnownSymbol s0, KnownSymbol s1, IdeRule k v) => Proxy s0 -> Proxy s1 -> [NormalizedFilePath] -> k -> Action () -runWithSignal msgStart msgEnd files rule = do +runWithSignal :: (KnownSymbol s0, KnownSymbol s1, IdeRule k i is v) => Proxy s0 -> Proxy s1 -> [InputPath i] -> k -> Action () +runWithSignal msgStart msgEnd inputFiles rule = do + let files = map unInputPath inputFiles ShakeExtras{ideTesting = Options.IdeTesting testing, lspEnv} <- getShakeExtras kickSignal testing lspEnv files msgStart - void $ uses rule files + void $ uses rule inputFiles kickSignal testing lspEnv files msgEnd diff --git a/hls-graph/hls-graph.cabal b/hls-graph/hls-graph.cabal index d5a9f781de..f9d3ca15ca 100644 --- a/hls-graph/hls-graph.cabal +++ b/hls-graph/hls-graph.cabal @@ -60,6 +60,7 @@ library Development.IDE.Graph.Internal.Key Development.IDE.Graph.Internal.Paths Development.IDE.Graph.Internal.Profile + Development.IDE.Graph.Internal.RuleInput Development.IDE.Graph.Internal.Rules Development.IDE.Graph.Internal.Types Development.IDE.Graph.KeyMap diff --git a/hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs b/hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs new file mode 100644 index 0000000000..093cd01269 --- /dev/null +++ b/hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs @@ -0,0 +1,17 @@ +{-# LANGUAGE TypeFamilies #-} +module Development.IDE.Graph.Internal.RuleInput where + +type ValidInputs = [Input] + +data Input + = ProjectHaskellFile + | DependencyHaskellFile + +type family RuleInput k :: ValidInputs + +class HasInput (i :: Input) (is :: ValidInputs) + +instance HasInput i (i : is) + +instance {-# OVERLAPPABLE #-} + HasInput i is => HasInput i (j : is) From bcc18e8d86efcbec03ef44f76fb1bbd4378df4c1 Mon Sep 17 00:00:00 2001 From: Elodie Lander Date: Sun, 8 Sep 2024 15:17:46 -0500 Subject: [PATCH 02/14] Add input constraints in Shake.hs --- ghcide/src/Development/IDE/Core/InputPath.hs | 4 +- ghcide/src/Development/IDE/Core/Shake.hs | 51 ++++++++++--------- .../IDE/Graph/Internal/RuleInput.hs | 1 + 3 files changed, 29 insertions(+), 27 deletions(-) diff --git a/ghcide/src/Development/IDE/Core/InputPath.hs b/ghcide/src/Development/IDE/Core/InputPath.hs index 7148ec682b..e807adbc0b 100644 --- a/ghcide/src/Development/IDE/Core/InputPath.hs +++ b/ghcide/src/Development/IDE/Core/InputPath.hs @@ -1,7 +1,7 @@ module Development.IDE.Core.InputPath where import Development.IDE.Graph.Internal.RuleInput (Input) -import Development.IDE (NormalizedFilePath) +import Language.LSP.Protocol.Types (NormalizedFilePath) newtype InputPath (i :: Input) = - InputPath { unInputPath :: NormalizedFilePath } \ No newline at end of file + InputPath { unInputPath :: NormalizedFilePath } deriving Eq \ No newline at end of file diff --git a/ghcide/src/Development/IDE/Core/Shake.hs b/ghcide/src/Development/IDE/Core/Shake.hs index 120d8a0515..d8091c6230 100644 --- a/ghcide/src/Development/IDE/Core/Shake.hs +++ b/ghcide/src/Development/IDE/Core/Shake.hs @@ -181,7 +181,7 @@ import System.FilePath hiding (makeRelative) import System.IO.Unsafe (unsafePerformIO) import System.Time.Extra import UnliftIO (MonadUnliftIO (withRunInIO)) -import Development.IDE.Graph.Internal.RuleInput (RuleInput, HasInput) +import Development.IDE.Graph.Internal.RuleInput (RuleInput, HasInput, Input(NoFile)) data Log @@ -345,7 +345,7 @@ type WithProgressFunc = forall a. type WithIndefiniteProgressFunc = forall a. T.Text -> LSP.ProgressCancellable -> IO a -> IO a -type GetStalePersistent = InputPath i -> IdeAction (Maybe (Dynamic,PositionDelta,Maybe Int32)) +type GetStalePersistent = NormalizedFilePath -> IdeAction (Maybe (Dynamic,PositionDelta,Maybe Int32)) getShakeExtras :: Action ShakeExtras getShakeExtras = do @@ -387,7 +387,7 @@ getPluginConfigAction plId = do -- This is called when we don't already have a result, or computing the rule failed. -- The result of this function will always be marked as 'stale', and a 'proper' rebuild of the rule will -- be queued if the rule hasn't run before. -addPersistentRule :: IdeRule k i is v => k -> (InputPath i -> IdeAction (Maybe (v,PositionDelta,Maybe Int32))) -> Rules () +addPersistentRule :: IdeRule k i is v => k -> (NormalizedFilePath -> IdeAction (Maybe (v,PositionDelta,Maybe Int32))) -> Rules () addPersistentRule k getVal = do ShakeExtras{persistentKeys} <- getShakeExtrasRules void $ liftIO $ atomically $ modifyTVar' persistentKeys $ insertKeyMap (newKey k) (fmap (fmap (first3 toDyn)) . getVal) @@ -456,7 +456,7 @@ getIdeOptionsIO ide = do -- | Return the most recent, potentially stale, value and a PositionMapping -- for the version of that value. lastValueIO :: IdeRule k i is v => ShakeExtras -> k -> InputPath i -> IO (Maybe (v, PositionMapping)) -lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k file = do +lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k (InputPath file) = do let readPersistent | IdeTesting testing <- ideTesting s -- Don't read stale persistent values in tests @@ -593,7 +593,7 @@ setValues :: IdeRule k i is v -> Value v -> Vector FileDiagnostic -> STM () -setValues state key file val diags = +setValues state key (InputPath file) val diags = STM.insert (ValueWithDiagnostics (fmap toDyn val) diags) (toKey key file) state @@ -618,7 +618,7 @@ getValues :: k -> InputPath i -> STM (Maybe (Value v, Vector FileDiagnostic)) -getValues state key file = do +getValues state key (InputPath file) = do STM.lookup (toKey key file) state >>= \case Nothing -> pure Nothing Just (ValueWithDiagnostics v diagsV) -> do @@ -1094,7 +1094,7 @@ useWithStaleFast' key file = do -- Async trigger the key to be built anyway because we want to -- keep updating the value in the key. - waitValue <- delayedAction $ mkDelayedAction ("C:" ++ show key ++ ":" ++ fromNormalizedFilePath file) Debug $ use key file + waitValue <- delayedAction $ mkDelayedAction ("C:" ++ show key ++ ":" ++ fromNormalizedFilePath (unInputPath file)) Debug $ use key file s@ShakeExtras{state} <- askShake r <- liftIO $ atomicallyNamed "useStateFast" $ getValues state key file @@ -1113,8 +1113,8 @@ useWithStaleFast' key file = do res <- lastValueIO s key file pure $ FastResult res waitValue -useNoFile :: IdeRule k i is v => k -> Action (Maybe v) -useNoFile key = use key emptyFilePath +useNoFile :: forall k is v. IdeRule k NoFile is v => k -> Action (Maybe v) +useNoFile key = use key (InputPath @NoFile emptyFilePath) -- Requests a rule if available. -- @@ -1125,8 +1125,8 @@ useNoFile key = use key emptyFilePath use_ :: IdeRule k i is v => k -> InputPath i -> Action v use_ key file = runIdentity <$> uses_ key (Identity file) -useNoFile_ :: IdeRule k i is v => k -> Action v -useNoFile_ key = use_ key emptyFilePath +useNoFile_ :: forall k is v. IdeRule k NoFile is v => k -> Action v +useNoFile_ key = use_ key (InputPath @NoFile emptyFilePath) -- |Plural version of `use_` -- @@ -1144,13 +1144,13 @@ uses_ key files = do -- | Plural version of 'use' uses :: (Traversable f, IdeRule k i is v) => k -> f (InputPath i) -> Action (f (Maybe v)) -uses key files = fmap (\(A value) -> currentValue value) <$> apply (fmap (Q . (key,)) files) +uses key files = fmap (\(A value) -> currentValue value) <$> apply (fmap (Q . (key,) . unInputPath) files) -- | Return the last computed result which might be stale. usesWithStale :: (Traversable f, IdeRule k i is v) => k -> f (InputPath i) -> Action (f (Maybe (v, PositionMapping))) usesWithStale key files = do - _ <- apply (fmap (Q . (key,)) files) + _ <- apply (fmap (Q . (key,) . unInputPath) files) -- We don't look at the result of the 'apply' since 'lastValue' will -- return the most recent successfully computed value regardless of -- whether the rule succeeded or not. @@ -1158,7 +1158,7 @@ usesWithStale key files = do useWithoutDependency :: IdeRule k i is v => k -> InputPath i -> Action (Maybe v) -useWithoutDependency key file = +useWithoutDependency key (InputPath file) = (\(Identity (A value)) -> currentValue value) <$> applyWithoutDependency (Identity (Q (key, file))) data RuleBody k i v @@ -1172,7 +1172,8 @@ data RuleBody k i v -- | Define a new Rule with early cutoff defineEarlyCutoff - :: IdeRule k i is v + :: forall k i is v + . IdeRule k i is v => Recorder (WithPriority Log) -> RuleBody k i v -> Rules () @@ -1181,35 +1182,35 @@ defineEarlyCutoff recorder (Rule op) = addRule $ \(Q (key, file)) (old :: Maybe let diagnostics ver diags = do traceDiagnostics diags updateFileDiagnostics recorder file ver (newKey key) extras . map (\(_,y,z) -> (y,z)) $ diags - defineEarlyCutoff' diagnostics (==) key file old mode $ const $ op key file + defineEarlyCutoff' diagnostics (==) key (InputPath @i file) old mode $ const $ op key (InputPath @i file) defineEarlyCutoff recorder (RuleNoDiagnostics op) = addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> otTracedAction key file mode traceA $ \traceDiagnostics -> do let diagnostics _ver diags = do traceDiagnostics diags mapM_ (logWith recorder Warning . LogDefineEarlyCutoffRuleNoDiagHasDiag) diags - defineEarlyCutoff' diagnostics (==) key file old mode $ const $ second (mempty,) <$> op key file + defineEarlyCutoff' diagnostics (==) key (InputPath @i file) old mode $ const $ second (mempty,) <$> op key (InputPath @i file) defineEarlyCutoff recorder RuleWithCustomNewnessCheck{..} = addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> otTracedAction key file mode traceA $ \ traceDiagnostics -> do let diagnostics _ver diags = do traceDiagnostics diags mapM_ (logWith recorder Warning . LogDefineEarlyCutoffRuleCustomNewnessHasDiag) diags - defineEarlyCutoff' diagnostics newnessCheck key file old mode $ - const $ second (mempty,) <$> build key file + defineEarlyCutoff' diagnostics newnessCheck key (InputPath @i file) old mode $ + const $ second (mempty,) <$> build key (InputPath @i file) defineEarlyCutoff recorder (RuleWithOldValue op) = addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> otTracedAction key file mode traceA $ \traceDiagnostics -> do extras <- getShakeExtras let diagnostics ver diags = do traceDiagnostics diags updateFileDiagnostics recorder file ver (newKey key) extras . map (\(_,y,z) -> (y,z)) $ diags - defineEarlyCutoff' diagnostics (==) key file old mode $ op key file + defineEarlyCutoff' diagnostics (==) key (InputPath @i file) old mode $ op key (InputPath @i file) -defineNoFile :: IdeRule k i is v => Recorder (WithPriority Log) -> (k -> Action v) -> Rules () +defineNoFile :: forall k i is v. IdeRule k i is v => Recorder (WithPriority Log) -> (k -> Action v) -> Rules () defineNoFile recorder f = defineNoDiagnostics recorder $ \k file -> do - if file == emptyFilePath then do res <- f k; return (Just res) else + if file == (InputPath @i emptyFilePath) then do res <- f k; return (Just res) else fail $ "Rule " ++ show k ++ " should always be called with the empty string for a file" -defineEarlyCutOffNoFile :: IdeRule k i is v => Recorder (WithPriority Log) -> (k -> Action (BS.ByteString, v)) -> Rules () -defineEarlyCutOffNoFile recorder f = defineEarlyCutoff recorder $ RuleNoDiagnostics $ \k (InputPath file) -> do - if file == emptyFilePath then do (hashString, res) <- f k; return (Just hashString, Just res) else +defineEarlyCutOffNoFile :: forall k i is v. IdeRule k i is v => Recorder (WithPriority Log) -> (k -> Action (BS.ByteString, v)) -> Rules () +defineEarlyCutOffNoFile recorder f = defineEarlyCutoff recorder $ RuleNoDiagnostics $ \k file -> do + if file == (InputPath @i emptyFilePath) then do (hashString, res) <- f k; return (Just hashString, Just res) else fail $ "Rule " ++ show k ++ " should always be called with the empty string for a file" defineEarlyCutoff' diff --git a/hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs b/hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs index 093cd01269..ff531760e8 100644 --- a/hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs +++ b/hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs @@ -6,6 +6,7 @@ type ValidInputs = [Input] data Input = ProjectHaskellFile | DependencyHaskellFile + | NoFile type family RuleInput k :: ValidInputs From bafb42aa1a9da6bdfd31928b39fdd448ace921bf Mon Sep 17 00:00:00 2001 From: Elodie Lander Date: Sun, 8 Sep 2024 16:04:25 -0500 Subject: [PATCH 03/14] Define RuleInput type instances --- ghcide/src/Development/IDE/Core/RuleTypes.hs | 29 ++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/ghcide/src/Development/IDE/Core/RuleTypes.hs b/ghcide/src/Development/IDE/Core/RuleTypes.hs index 3d60669f5c..53f3fe8f33 100644 --- a/ghcide/src/Development/IDE/Core/RuleTypes.hs +++ b/ghcide/src/Development/IDE/Core/RuleTypes.hs @@ -29,6 +29,7 @@ import Development.IDE.GHC.Compat.Util import Development.IDE.GHC.CoreFile import Development.IDE.GHC.Util import Development.IDE.Graph +import Development.IDE.Graph.Internal.RuleInput (Input(ProjectHaskellFile, DependencyHaskellFile, NoFile), RuleInput) import Development.IDE.Import.DependencyInformation import Development.IDE.Types.HscEnvEq (HscEnvEq) import Development.IDE.Types.KnownTargets @@ -65,21 +66,26 @@ encodeLinkableType (Just ObjectLinkable) = "2" -- | The parse tree for the file using GetFileContents type instance RuleResult GetParsedModule = ParsedModule +type instance RuleInput GetParsedModule = '[ProjectHaskellFile, DependencyHaskellFile] -- | The parse tree for the file using GetFileContents, -- all comments included using Opt_KeepRawTokenStream type instance RuleResult GetParsedModuleWithComments = ParsedModule +type instance RuleInput GetParsedModuleWithComments = '[ProjectHaskellFile, DependencyHaskellFile] type instance RuleResult GetModuleGraph = DependencyInformation +type instance RuleInput GetModuleGraph = '[ProjectHaskellFile] data GetKnownTargets = GetKnownTargets deriving (Show, Generic, Eq, Ord) instance Hashable GetKnownTargets instance NFData GetKnownTargets type instance RuleResult GetKnownTargets = KnownTargets +type instance RuleInput GetKnownTargets = '[NoFile] -- | Convert to Core, requires TypeCheck* type instance RuleResult GenerateCore = ModGuts +type instance RuleInput GenerateCore = '[ProjectHaskellFile] data GenerateCore = GenerateCore deriving (Eq, Show, Typeable, Generic) @@ -87,6 +93,7 @@ instance Hashable GenerateCore instance NFData GenerateCore type instance RuleResult GetLinkable = LinkableResult +type instance RuleInput GetLinkable = '[ProjectHaskellFile] data LinkableResult = LinkableResult @@ -112,6 +119,7 @@ instance Hashable GetImportMap instance NFData GetImportMap type instance RuleResult GetImportMap = ImportMap +type instance RuleInput GetImportMap = '[ProjectHaskellFile] newtype ImportMap = ImportMap { importMap :: M.Map ModuleName NormalizedFilePath -- ^ Where are the modules imported by this file located? } deriving stock Show @@ -232,12 +240,15 @@ instance Show HieAstResult where -- | The type checked version of this file, requires TypeCheck+ type instance RuleResult TypeCheck = TcModuleResult +type instance RuleInput TypeCheck = '[ProjectHaskellFile] -- | The uncompressed HieAST type instance RuleResult GetHieAst = HieAstResult +type instance RuleInput GetHieAst = '[ProjectHaskellFile, DependencyHaskellFile] -- | A IntervalMap telling us what is in scope at each point type instance RuleResult GetBindings = Bindings +type instance RuleInput GetBindings = '[ProjectHaskellFile] data DocAndTyThingMap = DKMap {getDocMap :: !DocMap, getTyThingMap :: !TyThingMap} instance NFData DocAndTyThingMap where @@ -247,39 +258,50 @@ instance Show DocAndTyThingMap where show = const "docmap" type instance RuleResult GetDocMap = DocAndTyThingMap +type instance RuleInput GetDocMap = '[ProjectHaskellFile] -- | A GHC session that we reuse. type instance RuleResult GhcSession = HscEnvEq +type instance RuleInput GhcSession = '[ProjectHaskellFile] -- | A GHC session preloaded with all the dependencies -- This rule is also responsible for calling ReportImportCycles for the direct dependencies type instance RuleResult GhcSessionDeps = HscEnvEq +type instance RuleInput GhcSessionDeps = '[ProjectHaskellFile] -- | Resolve the imports in a module to the file path of a module in the same package type instance RuleResult GetLocatedImports = [(Located ModuleName, Maybe ArtifactsLocation)] +type instance RuleInput GetLocatedImports = '[ProjectHaskellFile] -- | This rule is used to report import cycles. It depends on GetModuleGraph. -- We cannot report the cycles directly from GetModuleGraph since -- we can only report diagnostics for the current file. type instance RuleResult ReportImportCycles = () +type instance RuleInput ReportImportCycles = '[ProjectHaskellFile] -- | Read the module interface file from disk. Throws an error for VFS files. -- This is an internal rule, use 'GetModIface' instead. type instance RuleResult GetModIfaceFromDisk = HiFileResult +type instance RuleInput GetModIfaceFromDisk = '[ProjectHaskellFile] -- | GetModIfaceFromDisk and index the `.hie` file into the database. -- This is an internal rule, use 'GetModIface' instead. type instance RuleResult GetModIfaceFromDiskAndIndex = HiFileResult +type instance RuleInput GetModIfaceFromDiskAndIndex = '[ProjectHaskellFile] -- | Get a module interface details, either from an interface file or a typechecked module type instance RuleResult GetModIface = HiFileResult +type instance RuleInput GetModIface = '[ProjectHaskellFile, DependencyHaskellFile] -- | Get the contents of a file, either dirty (if the buffer is modified) or Nothing to mean use from disk. type instance RuleResult GetFileContents = (FileVersion, Maybe Text) +type instance RuleInput GetFileContents = '[ProjectHaskellFile, DependencyHaskellFile] type instance RuleResult GetFileExists = Bool +type instance RuleInput GetFileExists = '[ProjectHaskellFile, DependencyHaskellFile] type instance RuleResult AddWatchedFile = Bool +type instance RuleInput AddWatchedFile = '[ProjectHaskellFile] -- The Shake key type for getModificationTime queries @@ -309,6 +331,7 @@ pattern GetModificationTime = GetModificationTime_ {missingFileDiagnostics=True} -- | Get the modification time of a file. type instance RuleResult GetModificationTime = FileVersion +type instance RuleInput GetModificationTime = '[ProjectHaskellFile, DependencyHaskellFile] -- | Either the mtime from disk or an LSP version -- LSP versions always compare as greater than on disk versions @@ -351,6 +374,7 @@ instance Hashable IsFileOfInterestResult instance NFData IsFileOfInterestResult type instance RuleResult IsFileOfInterest = IsFileOfInterestResult +type instance RuleInput IsFileOfInterest = '[ProjectHaskellFile, DependencyHaskellFile] data ModSummaryResult = ModSummaryResult { msrModSummary :: !ModSummary @@ -373,9 +397,11 @@ instance NFData ModSummaryResult where -- | Generate a ModSummary that has enough information to be used to get .hi and .hie files. -- without needing to parse the entire source type instance RuleResult GetModSummary = ModSummaryResult +type instance RuleInput GetModSummary = '[ProjectHaskellFile, DependencyHaskellFile] -- | Generate a ModSummary with the timestamps and preprocessed content elided, for more successful early cutoff type instance RuleResult GetModSummaryWithoutTimestamps = ModSummaryResult +type instance RuleInput GetModSummaryWithoutTimestamps = '[ProjectHaskellFile, DependencyHaskellFile] data GetParsedModule = GetParsedModule deriving (Eq, Show, Typeable, Generic) @@ -394,6 +420,7 @@ instance NFData GetLocatedImports -- | Does this module need to be compiled? type instance RuleResult NeedsCompilation = Maybe LinkableType +type instance RuleInput NeedsCompilation = '[ProjectHaskellFile] data NeedsCompilation = NeedsCompilation deriving (Eq, Show, Typeable, Generic) @@ -487,6 +514,7 @@ instance Hashable GetClientSettings instance NFData GetClientSettings type instance RuleResult GetClientSettings = Hashed (Maybe Value) +type instance RuleInput GetClientSettings = '[ProjectHaskellFile] data AddWatchedFile = AddWatchedFile deriving (Eq, Show, Typeable, Generic) instance Hashable AddWatchedFile @@ -497,6 +525,7 @@ instance NFData AddWatchedFile -- thread killed exception issues, so we lift it to a full rule. -- https://github.com/digital-asset/daml/pull/2808#issuecomment-529639547 type instance RuleResult GhcSessionIO = IdeGhcSession +type instance RuleInput GhcSessionIO = '[ProjectHaskellFile] data IdeGhcSession = IdeGhcSession { loadSessionFun :: FilePath -> IO (IdeResult HscEnvEq, [FilePath]) From af47b30297433900c5b2c5f21d0ce39c1aa5c912 Mon Sep 17 00:00:00 2001 From: Elodie Lander Date: Sat, 14 Sep 2024 13:43:09 -0500 Subject: [PATCH 04/14] Fix Shake.hs --- ghcide/src/Development/IDE/Core/RuleTypes.hs | 2 +- ghcide/src/Development/IDE/Core/Shake.hs | 18 +++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/ghcide/src/Development/IDE/Core/RuleTypes.hs b/ghcide/src/Development/IDE/Core/RuleTypes.hs index 53f3fe8f33..6b46130be5 100644 --- a/ghcide/src/Development/IDE/Core/RuleTypes.hs +++ b/ghcide/src/Development/IDE/Core/RuleTypes.hs @@ -514,7 +514,7 @@ instance Hashable GetClientSettings instance NFData GetClientSettings type instance RuleResult GetClientSettings = Hashed (Maybe Value) -type instance RuleInput GetClientSettings = '[ProjectHaskellFile] +type instance RuleInput GetClientSettings = '[NoFile] data AddWatchedFile = AddWatchedFile deriving (Eq, Show, Typeable, Generic) instance Hashable AddWatchedFile diff --git a/ghcide/src/Development/IDE/Core/Shake.hs b/ghcide/src/Development/IDE/Core/Shake.hs index d8091c6230..da88a8c5ef 100644 --- a/ghcide/src/Development/IDE/Core/Shake.hs +++ b/ghcide/src/Development/IDE/Core/Shake.hs @@ -517,10 +517,14 @@ mappingForVersion allMappings file (Just (VFSVersion ver)) = do mappingForVersion _ _ _ = pure zeroMapping type IdeRule k i is v = - ( Shake.RuleResult k ~ v - , Shake.ShakeValue k + ( IdeValueRule k v , RuleInput k ~ is , HasInput i is + ) + +type IdeValueRule k v = + ( Shake.RuleResult k ~ v + , Shake.ShakeValue k , Show v , Typeable v , NFData v @@ -1156,9 +1160,9 @@ usesWithStale key files = do -- whether the rule succeeded or not. traverse (lastValue key) files -useWithoutDependency :: IdeRule k i is v - => k -> InputPath i -> Action (Maybe v) -useWithoutDependency key (InputPath file) = +useWithoutDependency :: IdeValueRule k v + => k -> NormalizedFilePath -> Action (Maybe v) +useWithoutDependency key file = (\(Identity (A value)) -> currentValue value) <$> applyWithoutDependency (Identity (Q (key, file))) data RuleBody k i v @@ -1288,8 +1292,8 @@ defineEarlyCutoff' doDiagnostics cmp key file mbOld mode action = do -> Maybe v -> InputPath i -> Action (Maybe FileVersion) - estimateFileVersionUnsafely _k v fp - | unInputPath fp == emptyFilePath = pure Nothing + estimateFileVersionUnsafely _k v (InputPath fp) + | fp == emptyFilePath = pure Nothing | Just Refl <- eqT @k @GetModificationTime = pure v -- GetModificationTime depends on these rules, so avoid creating a cycle | Just Refl <- eqT @k @AddWatchedFile = pure Nothing From 9a4e704b40aa58228742313374d0b76f6d9afd3b Mon Sep 17 00:00:00 2001 From: Elodie Lander Date: Sat, 14 Sep 2024 17:49:23 -0500 Subject: [PATCH 05/14] WIP Add InputPath type to rules --- ghcide/src/Development/IDE/Core/FileStore.hs | 29 +++---- ghcide/src/Development/IDE/Core/Rules.hs | 79 +++++++++++--------- 2 files changed, 59 insertions(+), 49 deletions(-) diff --git a/ghcide/src/Development/IDE/Core/FileStore.hs b/ghcide/src/Development/IDE/Core/FileStore.hs index 6c0cb875b0..7e86edbc68 100644 --- a/ghcide/src/Development/IDE/Core/FileStore.hs +++ b/ghcide/src/Development/IDE/Core/FileStore.hs @@ -69,6 +69,7 @@ import Language.LSP.VFS import System.FilePath import System.IO.Error import System.IO.Unsafe +import Development.IDE.Core.InputPath (InputPath (InputPath, unInputPath)) data Log @@ -88,16 +89,16 @@ instance Pretty Log where <+> pretty (fmap (fmap show) reverseDepPaths) LogShake msg -> pretty msg -addWatchedFileRule :: Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules () +addWatchedFileRule :: Recorder (WithPriority Log) -> (InputPath i -> Action Bool) -> Rules () addWatchedFileRule recorder isWatched = defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \AddWatchedFile f -> do isAlreadyWatched <- isWatched f - isWp <- isWorkspaceFile f + isWp <- isWorkspaceFile $ unInputPath f if isAlreadyWatched then pure (Just True) else if not isWp then pure (Just False) else do ShakeExtras{lspEnv} <- getShakeExtras case lspEnv of Just env -> fmap Just $ liftIO $ LSP.runLspT env $ - registerFileWatches [fromNormalizedFilePath f] + registerFileWatches [fromNormalizedFilePath (unInputPath f)] Nothing -> pure $ Just False @@ -107,12 +108,12 @@ getModificationTimeRule recorder = defineEarlyCutoff (cmapWithPrio LogShake reco getModificationTimeImpl :: Bool - -> NormalizedFilePath + -> InputPath i -> Action (Maybe BS.ByteString, ([FileDiagnostic], Maybe FileVersion)) getModificationTimeImpl missingFileDiags file = do - let file' = fromNormalizedFilePath file + let file' = fromNormalizedFilePath $ unInputPath file let wrap time = (Just $ LBS.toStrict $ B.encode $ toRational time, ([], Just $ ModificationTime time)) - mbVf <- getVirtualFile file + mbVf <- getVirtualFile $ unInputPath file case mbVf of Just (virtualFileVersion -> ver) -> do alwaysRerun @@ -124,7 +125,7 @@ getModificationTimeImpl missingFileDiags file = do -- but also need a dependency on IsFileOfInterest to reinstall -- alwaysRerun when the file becomes VFS void (use_ IsFileOfInterest file) - else if isInterface file + else if isInterface (unInputPath file) then -- interface files are tracked specially using the closed world assumption pure () else -- in all other cases we will need to freshly check the file system @@ -134,7 +135,7 @@ getModificationTimeImpl missingFileDiags file = do `catch` \(e :: IOException) -> do let err | isDoesNotExistError e = "File does not exist: " ++ file' | otherwise = "IO error while reading " ++ file' ++ ", " ++ displayException e - diag = ideErrorText file (T.pack err) + diag = ideErrorText (unInputPath file) (T.pack err) if isDoesNotExistError e && not missingFileDiags then return (Nothing, ([], Nothing)) else return (Nothing, ([diag], Nothing)) @@ -174,19 +175,19 @@ getFileContentsRule :: Recorder (WithPriority Log) -> Rules () getFileContentsRule recorder = define (cmapWithPrio LogShake recorder) $ \GetFileContents file -> getFileContentsImpl file getFileContentsImpl - :: NormalizedFilePath + :: InputPath i -> Action ([FileDiagnostic], Maybe (FileVersion, Maybe T.Text)) getFileContentsImpl file = do -- need to depend on modification time to introduce a dependency with Cutoff time <- use_ GetModificationTime file res <- do - mbVirtual <- getVirtualFile file + mbVirtual <- getVirtualFile $ unInputPath file pure $ virtualFileText <$> mbVirtual pure ([], Just (time, res)) -- | Returns the modification time and the contents. -- For VFS paths, the modification time is the current time. -getFileContents :: NormalizedFilePath -> Action (UTCTime, Maybe T.Text) +getFileContents :: InputPath i -> Action (UTCTime, Maybe T.Text) getFileContents f = do (fv, txt) <- use_ GetFileContents f modTime <- case modificationTime fv of @@ -196,11 +197,11 @@ getFileContents f = do liftIO $ case foi of IsFOI Modified{} -> getCurrentTime _ -> do - posix <- getModTime $ fromNormalizedFilePath f + posix <- getModTime $ fromNormalizedFilePath $ unInputPath f pure $ posixSecondsToUTCTime posix return (modTime, txt) -fileStoreRules :: Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules () +fileStoreRules :: Recorder (WithPriority Log) -> (InputPath i -> Action Bool) -> Rules () fileStoreRules recorder isWatched = do getModificationTimeRule recorder getFileContentsRule recorder @@ -239,7 +240,7 @@ typecheckParentsAction recorder nfp = do Nothing -> logWith recorder Info $ LogCouldNotIdentifyReverseDeps nfp Just rs -> do logWith recorder Info $ LogTypeCheckingReverseDeps nfp revs - void $ uses GetModIface rs + void $ uses GetModIface (map InputPath rs) -- | Note that some keys have been modified and restart the session -- Only valid if the virtual file system was initialised by LSP, as that diff --git a/ghcide/src/Development/IDE/Core/Rules.hs b/ghcide/src/Development/IDE/Core/Rules.hs index b0d61579cc..c285ca7f19 100644 --- a/ghcide/src/Development/IDE/Core/Rules.hs +++ b/ghcide/src/Development/IDE/Core/Rules.hs @@ -101,6 +101,7 @@ import Development.IDE.Core.FileExists hiding (Log, import Development.IDE.Core.FileStore (getFileContents, getModTime) import Development.IDE.Core.IdeConfiguration +import Development.IDE.Core.InputPath import Development.IDE.Core.OfInterest hiding (Log, LogShake) import Development.IDE.Core.PositionMapping @@ -125,6 +126,7 @@ import Development.IDE.GHC.Error import Development.IDE.GHC.Util hiding (modifyDynFlags) import Development.IDE.Graph +import Development.IDE.Graph.Internal.RuleInput import Development.IDE.Import.DependencyInformation import Development.IDE.Import.FindImports import qualified Development.IDE.Spans.AtPoint as AtPoint @@ -226,12 +228,14 @@ getSourceFileSource nfp = do Just source -> pure $ T.encodeUtf8 source -- | Parse the contents of a haskell file. -getParsedModule :: NormalizedFilePath -> Action (Maybe ParsedModule) +getParsedModule :: IdeRule GetParsedModule i is ParsedModule + => InputPath i -> Action (Maybe ParsedModule) getParsedModule = use GetParsedModule -- | Parse the contents of a haskell file, -- ensuring comments are preserved in annotations -getParsedModuleWithComments :: NormalizedFilePath -> Action (Maybe ParsedModule) +getParsedModuleWithComments :: IdeRule GetParsedModuleWithComments i is ParsedModule + => InputPath i -> Action (Maybe ParsedModule) getParsedModuleWithComments = use GetParsedModuleWithComments ------------------------------------------------------------ @@ -259,7 +263,7 @@ getParsedModuleRule recorder = -- We still parse with Haddocks whether Opt_Haddock is True or False to collect information -- but we no longer need to parse with and without Haddocks separately for above GHC90. - liftIO $ (fmap.fmap.fmap) reset_ms $ getParsedModuleDefinition hsc opt file (withOptHaddock ms) + liftIO $ (fmap.fmap.fmap) reset_ms $ getParsedModuleDefinition hsc opt (unInputPath file) (withOptHaddock ms) withOptHaddock :: ModSummary -> ModSummary withOptHaddock = withOption Opt_Haddock @@ -286,7 +290,7 @@ getParsedModuleWithCommentsRule recorder = let ms'' = ms' { ms_hspp_opts = modify_dflags $ ms_hspp_opts ms' } reset_ms pm = pm { pm_mod_summary = ms' } - liftIO $ fmap (fmap reset_ms) $ snd <$> getParsedModuleDefinition hsc opt file ms'' + liftIO $ fmap (fmap reset_ms) $ snd <$> getParsedModuleDefinition hsc opt (unInputPath file) ms'' getModifyDynFlags :: (DynFlagsModifications -> a) -> Action a getModifyDynFlags f = do @@ -367,7 +371,9 @@ execRawDepM act = -- | Given a target file path, construct the raw dependency results by following -- imports recursively. -rawDependencyInformation :: [NormalizedFilePath] -> Action (RawDependencyInformation, BootIdMap) +rawDependencyInformation :: forall i is + . IdeRule GetModSummaryWithoutTimestamps i is ModSummaryResult + => [InputPath i] -> Action (RawDependencyInformation, BootIdMap) rawDependencyInformation fs = do (rdi, ss) <- execRawDepM (goPlural fs) let bm = IntMap.foldrWithKey (updateBootMap rdi) IntMap.empty ss @@ -377,15 +383,15 @@ rawDependencyInformation fs = do mss <- lift $ (fmap.fmap) msrModSummary <$> uses GetModSummaryWithoutTimestamps ff zipWithM go ff mss - go :: NormalizedFilePath -- ^ Current module being processed + go :: InputPath i -- ^ Current module being processed -> Maybe ModSummary -- ^ ModSummary of the module -> RawDepM FilePathId go f mbModSum = do -- First check to see if we have already processed the FilePath -- If we have, just return its Id but don't update any of the state. -- Otherwise, we need to process its imports. - checkAlreadyProcessed f $ do - let al = modSummaryToArtifactsLocation f mbModSum + checkAlreadyProcessed (unInputPath f) $ do + let al = modSummaryToArtifactsLocation (unInputPath f) mbModSum -- Get a fresh FilePathId for the new file fId <- getFreshFid al -- Record this module and its location @@ -412,7 +418,7 @@ rawDependencyInformation fs = do (mns, ls) = unzip with_file -- Recursively process all the imports we just learnt about -- and get back a list of their FilePathIds - fids <- goPlural $ map artifactFilePath ls + fids <- goPlural $ map (InputPath @i . artifactFilePath) ls -- Associate together the ModuleName with the FilePathId let moduleImports' = map (,Nothing) no_file ++ zip mns (map Just fids) -- Insert into the map the information about this modules @@ -468,7 +474,7 @@ reportImportCyclesRule :: Recorder (WithPriority Log) -> Rules () reportImportCyclesRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \ReportImportCycles file -> fmap (\errs -> if null errs then (Just "1",([], Just ())) else (Nothing, (errs, Nothing))) $ do DependencyInformation{..} <- useNoFile_ GetModuleGraph - case pathToId depPathIdMap file of + case pathToId depPathIdMap (unInputPath file) of -- The header of the file does not parse, so it can't be part of any import cycles. Nothing -> pure [] Just fileId -> @@ -479,7 +485,7 @@ reportImportCyclesRule recorder = -- Convert cycles of files into cycles of module names forM cycles $ \(imp, files) -> do modNames <- forM files $ - getModuleName . idToPath depPathIdMap + getModuleName . InputPath . idToPath depPathIdMap pure $ toDiag imp $ sort modNames where cycleErrorInFile f (PartOfCycle imp fs) | f `elem` fs = Just (imp, fs) @@ -521,7 +527,8 @@ persistentHieFileRule recorder = addPersistentRule GetHieAst $ \file -> runMaybe del = deltaFromDiff (T.decodeUtf8 $ Compat.hie_hs_src res) currentSource pure (HAR (Compat.hie_module res) (Compat.hie_asts res) refmap mempty (HieFromDisk res),del,ver) -getHieAstRuleDefinition :: NormalizedFilePath -> HscEnv -> TcModuleResult -> Action (IdeResult HieAstResult) +getHieAstRuleDefinition :: IdeRule IsFileOfInterest i is IsFileOfInterestResult + => InputPath i -> HscEnv -> TcModuleResult -> Action (IdeResult HieAstResult) getHieAstRuleDefinition f hsc tmr = do (diags, masts) <- liftIO $ generateHieAsts hsc tmr se <- getShakeExtras @@ -531,13 +538,13 @@ getHieAstRuleDefinition f hsc tmr = do IsFOI Modified{firstOpen = False} -> do when (coerce $ ideTesting se) $ liftIO $ mRunLspT (lspEnv se) $ LSP.sendNotification (SMethod_CustomMethod (Proxy @"ghcide/reference/ready")) $ - toJSON $ fromNormalizedFilePath f + toJSON $ fromNormalizedFilePath $ unInputPath f pure [] _ | Just asts <- masts -> do - source <- getSourceFileSource f + source <- getSourceFileSource $ unInputPath f let exports = tcg_exports $ tmrTypechecked tmr modSummary = tmrModSummary tmr - liftIO $ writeAndIndexHieFile hsc se modSummary f exports asts source + liftIO $ writeAndIndexHieFile hsc se modSummary (unInputPath f) exports asts source _ -> pure [] let refmap = Compat.generateReferencesMap . Compat.getAsts <$> masts @@ -605,7 +612,7 @@ typeCheckRule recorder = define (cmapWithPrio LogShake recorder) $ \TypeCheck fi -- Keeping typechecked modules in memory for other files is -- very expensive. when (foi == NotFOI) $ - logWith recorder Logger.Warning $ LogTypecheckedFOI file + logWith recorder Logger.Warning $ LogTypecheckedFOI $ unInputPath file typeCheckRuleDefinition hsc pm knownFilesRule :: Recorder (WithPriority Log) -> Rules () @@ -617,13 +624,15 @@ knownFilesRule recorder = defineEarlyCutOffNoFile (cmapWithPrio LogShake recorde getModuleGraphRule :: Recorder (WithPriority Log) -> Rules () getModuleGraphRule recorder = defineEarlyCutOffNoFile (cmapWithPrio LogShake recorder) $ \GetModuleGraph -> do fs <- toKnownFiles <$> useNoFile_ GetKnownTargets - dependencyInfoForFiles (HashSet.toList fs) + dependencyInfoForFiles (map (InputPath @ProjectHaskellFile) $ HashSet.toList fs) -dependencyInfoForFiles :: [NormalizedFilePath] -> Action (BS.ByteString, DependencyInformation) +dependencyInfoForFiles :: forall i is + . IdeRule GetModSummaryWithoutTimestamps i is ModSummaryResult + => [InputPath i] -> Action (BS.ByteString, DependencyInformation) dependencyInfoForFiles fs = do (rawDepInfo, bm) <- rawDependencyInformation fs let (all_fs, _all_ids) = unzip $ HM.toList $ pathToIdMap $ rawPathIdMap rawDepInfo - msrs <- uses GetModSummaryWithoutTimestamps all_fs + msrs <- uses GetModSummaryWithoutTimestamps $ map (InputPath @i) all_fs let mss = map (fmap msrModSummary) msrs let deps = map (\i -> IM.lookup (getFilePathId i) (rawImports rawDepInfo)) _all_ids nodeKeys = IM.fromList $ catMaybes $ zipWith (\fi mms -> (getFilePathId fi,) . NodeKey_Module . msKey <$> mms) _all_ids mss @@ -649,7 +658,7 @@ typeCheckRuleDefinition hsc pm = do unlift <- askUnliftIO let dets = TypecheckHelpers - { getLinkables = unliftIO unlift . uses_ GetLinkable + { getLinkables = unliftIO unlift . uses_ GetLinkable . map InputPath } addUsageDependencies $ liftIO $ typecheckModule defer hsc dets pm @@ -659,7 +668,7 @@ typeCheckRuleDefinition hsc pm = do r@(_, mtc) <- a forM_ mtc $ \tc -> do used_files <- liftIO $ readIORef $ tcg_dependent_files $ tmrTypechecked tc - void $ uses_ GetModificationTime (map toNormalizedFilePath' used_files) + void $ uses_ GetModificationTime (map (InputPath . toNormalizedFilePath') used_files) return r -- | Get all the linkables stored in the graph, i.e. the ones we *do not* need to unload. @@ -695,7 +704,7 @@ loadGhcSession recorder ghcSessionDepsConfig = do defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \GhcSession file -> do IdeGhcSession{loadSessionFun} <- useNoFile_ GhcSessionIO -- loading is always returning a absolute path now - (val,deps) <- liftIO $ loadSessionFun $ fromNormalizedFilePath file + (val,deps) <- liftIO $ loadSessionFun $ fromNormalizedFilePath $ unInputPath file -- add the deps to the Shake graph let addDependency fp = do @@ -703,7 +712,7 @@ loadGhcSession recorder ghcSessionDepsConfig = do let nfp = toNormalizedFilePath' fp itExists <- getFileExists nfp when itExists $ void $ do - use_ GetModificationTime nfp + use_ GetModificationTime $ InputPath nfp mapM_ addDependency deps let cutoffHash = LBS.toStrict $ B.encode (hash (snd val)) @@ -730,7 +739,7 @@ instance Default GhcSessionDepsConfig where ghcSessionDepsDefinition :: -- | full mod summary Bool -> - GhcSessionDepsConfig -> HscEnvEq -> NormalizedFilePath -> Action (Maybe HscEnvEq) + GhcSessionDepsConfig -> HscEnvEq -> InputPath i -> Action (Maybe HscEnvEq) ghcSessionDepsDefinition fullModSummary GhcSessionDepsConfig{..} env file = do let hsc = hscEnv env @@ -743,8 +752,8 @@ ghcSessionDepsDefinition fullModSummary GhcSessionDepsConfig{..} env file = do then use_ GetModSummary file else use_ GetModSummaryWithoutTimestamps file - depSessions <- map hscEnv <$> uses_ (GhcSessionDeps_ fullModSummary) deps - ifaces <- uses_ GetModIface deps + depSessions <- map hscEnv <$> uses_ (GhcSessionDeps_ fullModSummary) (map InputPath deps) + ifaces <- uses_ GetModIface $ map InputPath deps let inLoadOrder = map (\HiFileResult{..} -> HomeModInfo hirModIface hirModDetails emptyHomeModInfoLinkable) ifaces mg <- do if fullModuleGraph @@ -755,7 +764,7 @@ ghcSessionDepsDefinition fullModSummary GhcSessionDepsConfig{..} env file = do -- also points to all the direct descendants of the current module. To get the keys for the descendants -- we must get their `ModSummary`s !final_deps <- do - dep_mss <- map msrModSummary <$> uses_ GetModSummaryWithoutTimestamps deps + dep_mss <- map msrModSummary <$> uses_ GetModSummaryWithoutTimestamps (map InputPath deps) return $!! map (NodeKey_Module . msKey) dep_mss let module_graph_nodes = nubOrdOn mkNodeKey (ModuleNode final_deps ms : concatMap mgModSummaries' mgs) @@ -788,9 +797,9 @@ getModIfaceFromDiskRule recorder = defineEarlyCutoff (cmapWithPrio LogShake reco recompInfo = RecompilationInfo { source_version = ver , old_value = m_old - , get_file_version = use GetModificationTime_{missingFileDiagnostics = False} - , get_linkable_hashes = \fs -> map (snd . fromJust . hirCoreFp) <$> uses_ GetModIface fs - , regenerate = regenerateHiFile session f ms + , get_file_version = use GetModificationTime_{missingFileDiagnostics = False} . InputPath + , get_linkable_hashes = \fs -> map (snd . fromJust . hirCoreFp) <$> uses_ GetModIface (map InputPath fs) + , regenerate = regenerateHiFile session (unInputPath f) ms } r <- loadInterface (hscEnv session) ms linkableType recompInfo case r of @@ -818,7 +827,7 @@ getModIfaceFromDiskAndIndexRule recorder = let ms = hirModSummary x hie_loc = Compat.ml_hie_file $ ms_location ms fileHash <- liftIO $ Util.getFileHash hie_loc - mrow <- liftIO $ withHieDb (\hieDb -> HieDb.lookupHieFileFromSource hieDb (fromNormalizedFilePath f)) + mrow <- liftIO $ withHieDb (\hieDb -> HieDb.lookupHieFileFromSource hieDb (fromNormalizedFilePath $ unInputPath f)) let hie_loc' = HieDb.hieModuleHieFile <$> mrow case mrow of Just row @@ -828,7 +837,7 @@ getModIfaceFromDiskAndIndexRule recorder = -- All good, the db has indexed the file when (coerce $ ideTesting se) $ liftIO $ mRunLspT (lspEnv se) $ LSP.sendNotification (SMethod_CustomMethod (Proxy @"ghcide/reference/ready")) $ - toJSON $ fromNormalizedFilePath f + toJSON $ fromNormalizedFilePath $ unInputPath f -- Not in db, must re-index _ -> do ehf <- liftIO $ runIdeAction "GetModIfaceFromDiskAndIndex" se $ runExceptT $ @@ -838,8 +847,8 @@ getModIfaceFromDiskAndIndexRule recorder = Left err -> fail $ "failed to read .hie file " ++ show hie_loc ++ ": " ++ displayException err -- can just re-index the file we read from disk Right hf -> liftIO $ do - logWith recorder Logger.Debug $ LogReindexingHieFile f - indexHieFile se ms f fileHash hf + logWith recorder Logger.Debug $ LogReindexingHieFile $ unInputPath f + indexHieFile se ms (unInputPath f) fileHash hf return (Just x) @@ -1089,7 +1098,7 @@ getLinkableRule recorder = return (fileHash <$ hmi, (warns, LinkableResult <$> hmi <*> pure fileHash)) -- | For now we always use bytecode unless something uses unboxed sums and tuples along with TH -getLinkableType :: NormalizedFilePath -> Action (Maybe LinkableType) +getLinkableType :: InputPath i -> Action (Maybe LinkableType) getLinkableType f = use_ NeedsCompilation f needsCompilationRule :: NormalizedFilePath -> Action (IdeResultNoDiagnosticsEarlyCutoff (Maybe LinkableType)) From 506e9584d369ee57a8ec5ede1a0bb312b924dc39 Mon Sep 17 00:00:00 2001 From: Elodie Lander Date: Sun, 15 Sep 2024 11:19:35 -0500 Subject: [PATCH 06/14] Try enumerating all instances --- ghcide/src/Development/IDE/Core/RuleTypes.hs | 58 +++++++++---------- .../IDE/Graph/Internal/RuleInput.hs | 14 +++-- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/ghcide/src/Development/IDE/Core/RuleTypes.hs b/ghcide/src/Development/IDE/Core/RuleTypes.hs index 6b46130be5..c48e7f3feb 100644 --- a/ghcide/src/Development/IDE/Core/RuleTypes.hs +++ b/ghcide/src/Development/IDE/Core/RuleTypes.hs @@ -29,7 +29,7 @@ import Development.IDE.GHC.Compat.Util import Development.IDE.GHC.CoreFile import Development.IDE.GHC.Util import Development.IDE.Graph -import Development.IDE.Graph.Internal.RuleInput (Input(ProjectHaskellFile, DependencyHaskellFile, NoFile), RuleInput) +import Development.IDE.Graph.Internal.RuleInput (Input(ProjectHaskellFile, DependencyHaskellFile, NoFile), RuleInput, ValidInputs(ProjectHaskellFilesOnly, AllHaskellFiles, NoFiles)) import Development.IDE.Import.DependencyInformation import Development.IDE.Types.HscEnvEq (HscEnvEq) import Development.IDE.Types.KnownTargets @@ -66,26 +66,26 @@ encodeLinkableType (Just ObjectLinkable) = "2" -- | The parse tree for the file using GetFileContents type instance RuleResult GetParsedModule = ParsedModule -type instance RuleInput GetParsedModule = '[ProjectHaskellFile, DependencyHaskellFile] +type instance RuleInput GetParsedModule = AllHaskellFiles -- | The parse tree for the file using GetFileContents, -- all comments included using Opt_KeepRawTokenStream type instance RuleResult GetParsedModuleWithComments = ParsedModule -type instance RuleInput GetParsedModuleWithComments = '[ProjectHaskellFile, DependencyHaskellFile] +type instance RuleInput GetParsedModuleWithComments = AllHaskellFiles type instance RuleResult GetModuleGraph = DependencyInformation -type instance RuleInput GetModuleGraph = '[ProjectHaskellFile] +type instance RuleInput GetModuleGraph = ProjectHaskellFilesOnly data GetKnownTargets = GetKnownTargets deriving (Show, Generic, Eq, Ord) instance Hashable GetKnownTargets instance NFData GetKnownTargets type instance RuleResult GetKnownTargets = KnownTargets -type instance RuleInput GetKnownTargets = '[NoFile] +type instance RuleInput GetKnownTargets = NoFiles -- | Convert to Core, requires TypeCheck* type instance RuleResult GenerateCore = ModGuts -type instance RuleInput GenerateCore = '[ProjectHaskellFile] +type instance RuleInput GenerateCore = ProjectHaskellFilesOnly data GenerateCore = GenerateCore deriving (Eq, Show, Typeable, Generic) @@ -93,7 +93,7 @@ instance Hashable GenerateCore instance NFData GenerateCore type instance RuleResult GetLinkable = LinkableResult -type instance RuleInput GetLinkable = '[ProjectHaskellFile] +type instance RuleInput GetLinkable = ProjectHaskellFilesOnly data LinkableResult = LinkableResult @@ -119,7 +119,7 @@ instance Hashable GetImportMap instance NFData GetImportMap type instance RuleResult GetImportMap = ImportMap -type instance RuleInput GetImportMap = '[ProjectHaskellFile] +type instance RuleInput GetImportMap = ProjectHaskellFilesOnly newtype ImportMap = ImportMap { importMap :: M.Map ModuleName NormalizedFilePath -- ^ Where are the modules imported by this file located? } deriving stock Show @@ -240,15 +240,15 @@ instance Show HieAstResult where -- | The type checked version of this file, requires TypeCheck+ type instance RuleResult TypeCheck = TcModuleResult -type instance RuleInput TypeCheck = '[ProjectHaskellFile] +type instance RuleInput TypeCheck = ProjectHaskellFilesOnly -- | The uncompressed HieAST type instance RuleResult GetHieAst = HieAstResult -type instance RuleInput GetHieAst = '[ProjectHaskellFile, DependencyHaskellFile] +type instance RuleInput GetHieAst = AllHaskellFiles -- | A IntervalMap telling us what is in scope at each point type instance RuleResult GetBindings = Bindings -type instance RuleInput GetBindings = '[ProjectHaskellFile] +type instance RuleInput GetBindings = ProjectHaskellFilesOnly data DocAndTyThingMap = DKMap {getDocMap :: !DocMap, getTyThingMap :: !TyThingMap} instance NFData DocAndTyThingMap where @@ -258,50 +258,50 @@ instance Show DocAndTyThingMap where show = const "docmap" type instance RuleResult GetDocMap = DocAndTyThingMap -type instance RuleInput GetDocMap = '[ProjectHaskellFile] +type instance RuleInput GetDocMap = ProjectHaskellFilesOnly -- | A GHC session that we reuse. type instance RuleResult GhcSession = HscEnvEq -type instance RuleInput GhcSession = '[ProjectHaskellFile] +type instance RuleInput GhcSession = ProjectHaskellFilesOnly -- | A GHC session preloaded with all the dependencies -- This rule is also responsible for calling ReportImportCycles for the direct dependencies type instance RuleResult GhcSessionDeps = HscEnvEq -type instance RuleInput GhcSessionDeps = '[ProjectHaskellFile] +type instance RuleInput GhcSessionDeps = ProjectHaskellFilesOnly -- | Resolve the imports in a module to the file path of a module in the same package type instance RuleResult GetLocatedImports = [(Located ModuleName, Maybe ArtifactsLocation)] -type instance RuleInput GetLocatedImports = '[ProjectHaskellFile] +type instance RuleInput GetLocatedImports = ProjectHaskellFilesOnly -- | This rule is used to report import cycles. It depends on GetModuleGraph. -- We cannot report the cycles directly from GetModuleGraph since -- we can only report diagnostics for the current file. type instance RuleResult ReportImportCycles = () -type instance RuleInput ReportImportCycles = '[ProjectHaskellFile] +type instance RuleInput ReportImportCycles = ProjectHaskellFilesOnly -- | Read the module interface file from disk. Throws an error for VFS files. -- This is an internal rule, use 'GetModIface' instead. type instance RuleResult GetModIfaceFromDisk = HiFileResult -type instance RuleInput GetModIfaceFromDisk = '[ProjectHaskellFile] +type instance RuleInput GetModIfaceFromDisk = ProjectHaskellFilesOnly -- | GetModIfaceFromDisk and index the `.hie` file into the database. -- This is an internal rule, use 'GetModIface' instead. type instance RuleResult GetModIfaceFromDiskAndIndex = HiFileResult -type instance RuleInput GetModIfaceFromDiskAndIndex = '[ProjectHaskellFile] +type instance RuleInput GetModIfaceFromDiskAndIndex = ProjectHaskellFilesOnly -- | Get a module interface details, either from an interface file or a typechecked module type instance RuleResult GetModIface = HiFileResult -type instance RuleInput GetModIface = '[ProjectHaskellFile, DependencyHaskellFile] +type instance RuleInput GetModIface = AllHaskellFiles -- | Get the contents of a file, either dirty (if the buffer is modified) or Nothing to mean use from disk. type instance RuleResult GetFileContents = (FileVersion, Maybe Text) -type instance RuleInput GetFileContents = '[ProjectHaskellFile, DependencyHaskellFile] +type instance RuleInput GetFileContents = AllHaskellFiles type instance RuleResult GetFileExists = Bool -type instance RuleInput GetFileExists = '[ProjectHaskellFile, DependencyHaskellFile] +type instance RuleInput GetFileExists = AllHaskellFiles type instance RuleResult AddWatchedFile = Bool -type instance RuleInput AddWatchedFile = '[ProjectHaskellFile] +type instance RuleInput AddWatchedFile = ProjectHaskellFilesOnly -- The Shake key type for getModificationTime queries @@ -331,7 +331,7 @@ pattern GetModificationTime = GetModificationTime_ {missingFileDiagnostics=True} -- | Get the modification time of a file. type instance RuleResult GetModificationTime = FileVersion -type instance RuleInput GetModificationTime = '[ProjectHaskellFile, DependencyHaskellFile] +type instance RuleInput GetModificationTime = AllHaskellFiles -- | Either the mtime from disk or an LSP version -- LSP versions always compare as greater than on disk versions @@ -374,7 +374,7 @@ instance Hashable IsFileOfInterestResult instance NFData IsFileOfInterestResult type instance RuleResult IsFileOfInterest = IsFileOfInterestResult -type instance RuleInput IsFileOfInterest = '[ProjectHaskellFile, DependencyHaskellFile] +type instance RuleInput IsFileOfInterest = AllHaskellFiles data ModSummaryResult = ModSummaryResult { msrModSummary :: !ModSummary @@ -397,11 +397,11 @@ instance NFData ModSummaryResult where -- | Generate a ModSummary that has enough information to be used to get .hi and .hie files. -- without needing to parse the entire source type instance RuleResult GetModSummary = ModSummaryResult -type instance RuleInput GetModSummary = '[ProjectHaskellFile, DependencyHaskellFile] +type instance RuleInput GetModSummary = AllHaskellFiles -- | Generate a ModSummary with the timestamps and preprocessed content elided, for more successful early cutoff type instance RuleResult GetModSummaryWithoutTimestamps = ModSummaryResult -type instance RuleInput GetModSummaryWithoutTimestamps = '[ProjectHaskellFile, DependencyHaskellFile] +type instance RuleInput GetModSummaryWithoutTimestamps = AllHaskellFiles data GetParsedModule = GetParsedModule deriving (Eq, Show, Typeable, Generic) @@ -420,7 +420,7 @@ instance NFData GetLocatedImports -- | Does this module need to be compiled? type instance RuleResult NeedsCompilation = Maybe LinkableType -type instance RuleInput NeedsCompilation = '[ProjectHaskellFile] +type instance RuleInput NeedsCompilation = ProjectHaskellFilesOnly data NeedsCompilation = NeedsCompilation deriving (Eq, Show, Typeable, Generic) @@ -514,7 +514,7 @@ instance Hashable GetClientSettings instance NFData GetClientSettings type instance RuleResult GetClientSettings = Hashed (Maybe Value) -type instance RuleInput GetClientSettings = '[NoFile] +type instance RuleInput GetClientSettings = NoFiles data AddWatchedFile = AddWatchedFile deriving (Eq, Show, Typeable, Generic) instance Hashable AddWatchedFile @@ -525,7 +525,7 @@ instance NFData AddWatchedFile -- thread killed exception issues, so we lift it to a full rule. -- https://github.com/digital-asset/daml/pull/2808#issuecomment-529639547 type instance RuleResult GhcSessionIO = IdeGhcSession -type instance RuleInput GhcSessionIO = '[ProjectHaskellFile] +type instance RuleInput GhcSessionIO = ProjectHaskellFilesOnly data IdeGhcSession = IdeGhcSession { loadSessionFun :: FilePath -> IO (IdeResult HscEnvEq, [FilePath]) diff --git a/hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs b/hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs index ff531760e8..9b77f2f777 100644 --- a/hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs +++ b/hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs @@ -1,7 +1,10 @@ {-# LANGUAGE TypeFamilies #-} module Development.IDE.Graph.Internal.RuleInput where -type ValidInputs = [Input] +data ValidInputs + = ProjectHaskellFilesOnly + | AllHaskellFiles + | NoFiles data Input = ProjectHaskellFile @@ -12,7 +15,10 @@ type family RuleInput k :: ValidInputs class HasInput (i :: Input) (is :: ValidInputs) -instance HasInput i (i : is) +instance HasInput ProjectHaskellFile ProjectHaskellFilesOnly -instance {-# OVERLAPPABLE #-} - HasInput i is => HasInput i (j : is) +instance HasInput ProjectHaskellFile AllHaskellFiles + +instance HasInput DependencyHaskellFile AllHaskellFiles + +instance HasInput NoFile NoFiles From 4053e63e86561db4527c04ab44536a979505819b Mon Sep 17 00:00:00 2001 From: Elodie Lander Date: Thu, 26 Sep 2024 20:53:36 -0500 Subject: [PATCH 07/14] WIP continue following type errors --- ghcide/src/Development/IDE/Core/FileExists.hs | 21 +++++++--- ghcide/src/Development/IDE/Core/FileStore.hs | 40 ++++++++++++------- ghcide/src/Development/IDE/Core/InputPath.hs | 23 +++++++++-- ghcide/src/Development/IDE/Core/RuleTypes.hs | 4 +- 4 files changed, 63 insertions(+), 25 deletions(-) diff --git a/ghcide/src/Development/IDE/Core/FileExists.hs b/ghcide/src/Development/IDE/Core/FileExists.hs index 280cd14028..1678ca3cbf 100644 --- a/ghcide/src/Development/IDE/Core/FileExists.hs +++ b/ghcide/src/Development/IDE/Core/FileExists.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} @@ -26,6 +27,7 @@ import Development.IDE.Core.RuleTypes import Development.IDE.Core.Shake hiding (Log) import qualified Development.IDE.Core.Shake as Shake import Development.IDE.Graph +import Development.IDE.Graph.Internal.RuleInput import Development.IDE.Types.Location import Development.IDE.Types.Options import Development.IDE.Types.Shake (toKey) @@ -39,6 +41,7 @@ import Language.LSP.Server hiding (getVirtualFile) import qualified StmContainers.Map as STM import qualified System.Directory as Dir import qualified System.FilePath.Glob as Glob +import Development.IDE.Core.InputPath (InputPath (InputPath)) {- Note [File existence cache and LSP file watchers] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -133,7 +136,7 @@ fromChange FileChangeType_Changed = Nothing ------------------------------------------------------------------------------------- -- | Returns True if the file exists -getFileExists :: NormalizedFilePath -> Action Bool +getFileExists :: InputPath i -> Action Bool getFileExists fp = use_ GetFileExists fp {- Note [Which files should we watch?] @@ -192,12 +195,15 @@ fileExistsRules recorder lspEnv = do then fileExistsRulesFast recorder isWatched else fileExistsRulesSlow recorder - fileStoreRules (cmapWithPrio LogFileStore recorder) isWatched + fileStoreRules (cmapWithPrio LogFileStore recorder) (\(InputPath f) -> isWatched f) -- Requires an lsp client that provides WatchedFiles notifications, but assumes that this has already been checked. -fileExistsRulesFast :: Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules () +fileExistsRulesFast :: forall i. HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules () fileExistsRulesFast recorder isWatched = - defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \GetFileExists file -> do + defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics runGetFileExists + where + runGetFileExists :: GetFileExists -> InputPath i -> Action (Maybe BS.ByteString, Maybe Bool) + runGetFileExists GetFileExists (InputPath file) = do isWF <- isWatched file if isWF then fileExistsFast file @@ -236,9 +242,12 @@ fileExistsFast file = do summarizeExists :: Bool -> Maybe BS.ByteString summarizeExists x = Just $ if x then BS.singleton 1 else BS.empty -fileExistsRulesSlow :: Recorder (WithPriority Log) -> Rules () +fileExistsRulesSlow :: forall i. HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> Rules () fileExistsRulesSlow recorder = - defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \GetFileExists file -> fileExistsSlow file + defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics runGetFileExists + where + runGetFileExists :: GetFileExists -> InputPath i -> Action (Maybe BS.ByteString, Maybe Bool) + runGetFileExists GetFileExists (InputPath file) = fileExistsSlow file fileExistsSlow :: NormalizedFilePath -> Action (Maybe BS.ByteString, Maybe Bool) fileExistsSlow file = do diff --git a/ghcide/src/Development/IDE/Core/FileStore.hs b/ghcide/src/Development/IDE/Core/FileStore.hs index 7e86edbc68..31d6a918e1 100644 --- a/ghcide/src/Development/IDE/Core/FileStore.hs +++ b/ghcide/src/Development/IDE/Core/FileStore.hs @@ -1,5 +1,6 @@ -- Copyright (c) 2019 The DAML Authors. All rights reserved. -- SPDX-License-Identifier: Apache-2.0 +{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE TypeFamilies #-} module Development.IDE.Core.FileStore( @@ -42,6 +43,7 @@ import Development.IDE.Core.Shake hiding (Log) import qualified Development.IDE.Core.Shake as Shake import Development.IDE.GHC.Orphans () import Development.IDE.Graph +import Development.IDE.Graph.Internal.RuleInput import Development.IDE.Import.DependencyInformation import Development.IDE.Types.Diagnostics import Development.IDE.Types.Location @@ -69,7 +71,7 @@ import Language.LSP.VFS import System.FilePath import System.IO.Error import System.IO.Unsafe -import Development.IDE.Core.InputPath (InputPath (InputPath, unInputPath)) +import Development.IDE.Core.InputPath (InputPath (unInputPath), partitionInputs, PartitionedInputs (projectFiles, dependencyFiles)) data Log @@ -89,7 +91,7 @@ instance Pretty Log where <+> pretty (fmap (fmap show) reverseDepPaths) LogShake msg -> pretty msg -addWatchedFileRule :: Recorder (WithPriority Log) -> (InputPath i -> Action Bool) -> Rules () +addWatchedFileRule :: HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> (InputPath i -> Action Bool) -> Rules () addWatchedFileRule recorder isWatched = defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \AddWatchedFile f -> do isAlreadyWatched <- isWatched f isWp <- isWorkspaceFile $ unInputPath f @@ -102,12 +104,16 @@ addWatchedFileRule recorder isWatched = defineNoDiagnostics (cmapWithPrio LogSha Nothing -> pure $ Just False -getModificationTimeRule :: Recorder (WithPriority Log) -> Rules () -getModificationTimeRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \(GetModificationTime_ missingFileDiags) file -> - getModificationTimeImpl missingFileDiags file +getModificationTimeRule :: forall i. HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> Rules () +getModificationTimeRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule runGetModificationTimeImpl + where + runGetModificationTimeImpl :: GetModificationTime -> InputPath i -> Action (Maybe BS.ByteString, ([FileDiagnostic], Maybe FileVersion)) + runGetModificationTimeImpl (GetModificationTime_ missingFileDiags) file = + getModificationTimeImpl missingFileDiags file getModificationTimeImpl - :: Bool + :: HasInput i AllHaskellFiles + => Bool -> InputPath i -> Action (Maybe BS.ByteString, ([FileDiagnostic], Maybe FileVersion)) getModificationTimeImpl missingFileDiags file = do @@ -171,11 +177,15 @@ modificationTime :: FileVersion -> Maybe UTCTime modificationTime VFSVersion{} = Nothing modificationTime (ModificationTime posix) = Just $ posixSecondsToUTCTime posix -getFileContentsRule :: Recorder (WithPriority Log) -> Rules () -getFileContentsRule recorder = define (cmapWithPrio LogShake recorder) $ \GetFileContents file -> getFileContentsImpl file +getFileContentsRule :: forall i. HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> Rules () +getFileContentsRule recorder = define (cmapWithPrio LogShake recorder) runGetFileContentsImpl + where + runGetFileContentsImpl :: GetFileContents -> InputPath i -> Action ([FileDiagnostic], Maybe (FileVersion, Maybe T.Text)) + runGetFileContentsImpl GetFileContents file = getFileContentsImpl file getFileContentsImpl - :: InputPath i + :: HasInput i AllHaskellFiles + => InputPath i -> Action ([FileDiagnostic], Maybe (FileVersion, Maybe T.Text)) getFileContentsImpl file = do -- need to depend on modification time to introduce a dependency with Cutoff @@ -187,7 +197,7 @@ getFileContentsImpl file = do -- | Returns the modification time and the contents. -- For VFS paths, the modification time is the current time. -getFileContents :: InputPath i -> Action (UTCTime, Maybe T.Text) +getFileContents :: HasInput i AllHaskellFiles => InputPath i -> Action (UTCTime, Maybe T.Text) getFileContents f = do (fv, txt) <- use_ GetFileContents f modTime <- case modificationTime fv of @@ -201,10 +211,10 @@ getFileContents f = do pure $ posixSecondsToUTCTime posix return (modTime, txt) -fileStoreRules :: Recorder (WithPriority Log) -> (InputPath i -> Action Bool) -> Rules () +fileStoreRules :: forall i. HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> (InputPath i -> Action Bool) -> Rules () fileStoreRules recorder isWatched = do - getModificationTimeRule recorder - getFileContentsRule recorder + getModificationTimeRule @i recorder + getFileContentsRule @i recorder addWatchedFileRule recorder isWatched -- | Note that some buffer for a specific file has been modified but not @@ -240,7 +250,9 @@ typecheckParentsAction recorder nfp = do Nothing -> logWith recorder Info $ LogCouldNotIdentifyReverseDeps nfp Just rs -> do logWith recorder Info $ LogTypeCheckingReverseDeps nfp revs - void $ uses GetModIface (map InputPath rs) + let partitionedInputs = partitionInputs rs + void $ uses GetModIface (projectFiles partitionedInputs) + void $ uses GetModIface (dependencyFiles partitionedInputs) -- | Note that some keys have been modified and restart the session -- Only valid if the virtual file system was initialised by LSP, as that diff --git a/ghcide/src/Development/IDE/Core/InputPath.hs b/ghcide/src/Development/IDE/Core/InputPath.hs index e807adbc0b..5dc6f127f6 100644 --- a/ghcide/src/Development/IDE/Core/InputPath.hs +++ b/ghcide/src/Development/IDE/Core/InputPath.hs @@ -1,7 +1,24 @@ module Development.IDE.Core.InputPath where -import Development.IDE.Graph.Internal.RuleInput (Input) -import Language.LSP.Protocol.Types (NormalizedFilePath) +import Development.IDE.Graph.Internal.RuleInput (Input(ProjectHaskellFile, DependencyHaskellFile)) +import Language.LSP.Protocol.Types (NormalizedFilePath, fromNormalizedFilePath) +import Data.List (isInfixOf) +import System.FilePath (splitDirectories) newtype InputPath (i :: Input) = - InputPath { unInputPath :: NormalizedFilePath } deriving Eq \ No newline at end of file + InputPath { unInputPath :: NormalizedFilePath } deriving Eq + +data PartitionedInputs = PartitionedInputs + { projectFiles :: [InputPath ProjectHaskellFile] + , dependencyFiles :: [InputPath DependencyHaskellFile] + } + +partitionInputs :: [NormalizedFilePath] -> PartitionedInputs +partitionInputs = foldr classifyInputPath emptyInputs + where + classifyInputPath :: NormalizedFilePath -> PartitionedInputs -> PartitionedInputs + classifyInputPath nfp inputs@(PartitionedInputs projectInputs dependencyInputs) = + case [".hls", "dependencies"] `isInfixOf` splitDirectories (fromNormalizedFilePath nfp) of + True -> inputs{ projectFiles = InputPath nfp : projectInputs } + False -> inputs{ dependencyFiles = InputPath nfp : dependencyInputs } + emptyInputs = PartitionedInputs [] [] \ No newline at end of file diff --git a/ghcide/src/Development/IDE/Core/RuleTypes.hs b/ghcide/src/Development/IDE/Core/RuleTypes.hs index c48e7f3feb..cbc72ecb7e 100644 --- a/ghcide/src/Development/IDE/Core/RuleTypes.hs +++ b/ghcide/src/Development/IDE/Core/RuleTypes.hs @@ -74,7 +74,7 @@ type instance RuleResult GetParsedModuleWithComments = ParsedModule type instance RuleInput GetParsedModuleWithComments = AllHaskellFiles type instance RuleResult GetModuleGraph = DependencyInformation -type instance RuleInput GetModuleGraph = ProjectHaskellFilesOnly +type instance RuleInput GetModuleGraph = NoFiles data GetKnownTargets = GetKnownTargets deriving (Show, Generic, Eq, Ord) @@ -301,7 +301,7 @@ type instance RuleResult GetFileExists = Bool type instance RuleInput GetFileExists = AllHaskellFiles type instance RuleResult AddWatchedFile = Bool -type instance RuleInput AddWatchedFile = ProjectHaskellFilesOnly +type instance RuleInput AddWatchedFile = AllHaskellFiles -- The Shake key type for getModificationTime queries From a338ce6d5fd20b975d5026f3c610f883cdb5effa Mon Sep 17 00:00:00 2001 From: Elodie Lander Date: Sat, 5 Oct 2024 16:17:31 -0500 Subject: [PATCH 08/14] Kick the can --- ghcide/src/Development/IDE/Core/FileExists.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ghcide/src/Development/IDE/Core/FileExists.hs b/ghcide/src/Development/IDE/Core/FileExists.hs index 1678ca3cbf..39533182ae 100644 --- a/ghcide/src/Development/IDE/Core/FileExists.hs +++ b/ghcide/src/Development/IDE/Core/FileExists.hs @@ -136,7 +136,7 @@ fromChange FileChangeType_Changed = Nothing ------------------------------------------------------------------------------------- -- | Returns True if the file exists -getFileExists :: InputPath i -> Action Bool +getFileExists :: HasInput i AllHaskellFiles => InputPath i -> Action Bool getFileExists fp = use_ GetFileExists fp {- Note [Which files should we watch?] @@ -170,7 +170,7 @@ allExtensions opts = [extIncBoot | ext <- optExtensions opts, extIncBoot <- [ext -- | Installs the 'getFileExists' rules. -- Provides a fast implementation if client supports dynamic watched files. -- Creates a global state as a side effect in that case. -fileExistsRules :: Recorder (WithPriority Log) -> Maybe (LanguageContextEnv Config) -> Rules () +fileExistsRules :: forall i. HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> Maybe (LanguageContextEnv Config) -> Rules () fileExistsRules recorder lspEnv = do supportsWatchedFiles <- case lspEnv of Nothing -> pure False @@ -192,10 +192,10 @@ fileExistsRules recorder lspEnv = do else const $ pure False if supportsWatchedFiles - then fileExistsRulesFast recorder isWatched - else fileExistsRulesSlow recorder + then fileExistsRulesFast @i recorder isWatched + else fileExistsRulesSlow @i recorder - fileStoreRules (cmapWithPrio LogFileStore recorder) (\(InputPath f) -> isWatched f) + fileStoreRules @i (cmapWithPrio LogFileStore recorder) (\(InputPath f) -> isWatched f) -- Requires an lsp client that provides WatchedFiles notifications, but assumes that this has already been checked. fileExistsRulesFast :: forall i. HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules () From 755f0ddca9df8bbe56f755c60a124dd5729cc0ef Mon Sep 17 00:00:00 2001 From: Elodie Lander Date: Sun, 27 Oct 2024 05:46:08 -0500 Subject: [PATCH 09/14] Attempt going back to monomorphic Q --- ghcide/src/Development/IDE/Core/InputPath.hs | 67 +++-- ghcide/src/Development/IDE/Core/RuleTypes.hs | 58 ++--- ghcide/src/Development/IDE/Core/Shake.hs | 228 +++++++++--------- hls-graph/hls-graph.cabal | 1 - hls-graph/src/Development/IDE/Graph.hs | 2 +- .../IDE/Graph/Internal/RuleInput.hs | 24 -- .../Development/IDE/Graph/Internal/Rules.hs | 1 + 7 files changed, 194 insertions(+), 187 deletions(-) delete mode 100644 hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs diff --git a/ghcide/src/Development/IDE/Core/InputPath.hs b/ghcide/src/Development/IDE/Core/InputPath.hs index 5dc6f127f6..9bb7cdc861 100644 --- a/ghcide/src/Development/IDE/Core/InputPath.hs +++ b/ghcide/src/Development/IDE/Core/InputPath.hs @@ -1,24 +1,59 @@ +{-# LANGUAGE DerivingStrategies #-} + module Development.IDE.Core.InputPath where -import Development.IDE.Graph.Internal.RuleInput (Input(ProjectHaskellFile, DependencyHaskellFile)) -import Language.LSP.Protocol.Types (NormalizedFilePath, fromNormalizedFilePath) +import Control.DeepSeq +import Data.Hashable import Data.List (isInfixOf) +import Data.Typeable +import Development.IDE.Types.Location (emptyFilePath) +import Language.LSP.Protocol.Types (NormalizedFilePath, fromNormalizedFilePath) import System.FilePath (splitDirectories) -newtype InputPath (i :: Input) = - InputPath { unInputPath :: NormalizedFilePath } deriving Eq +data InputClass + = ProjectHaskellFiles + | AllHaskellFiles + +newtype InputPath (i :: InputClass) = + InputPath { unInputPath :: NormalizedFilePath } + deriving newtype (Eq, Hashable, NFData, Typeable, Show) + +class HasNormalizedFilePath input where + getNormalizedFilePath :: input -> NormalizedFilePath + +instance HasNormalizedFilePath (InputPath ProjectHaskellFiles) where + getNormalizedFilePath (InputPath nfp) = nfp + +instance HasNormalizedFilePath (InputPath AllHaskellFiles) where + getNormalizedFilePath (InputPath nfp) = nfp + +instance HasNormalizedFilePath () where + getNormalizedFilePath _ = emptyFilePath -data PartitionedInputs = PartitionedInputs - { projectFiles :: [InputPath ProjectHaskellFile] - , dependencyFiles :: [InputPath DependencyHaskellFile] - } +-- All Haskell files are valid, and we assume all +-- files are Haskell files (for now) so there is +-- no need to filter out any FilePaths. +classifyAllHaskellInputs :: [NormalizedFilePath] -> [InputPath ProjectHaskellFiles] +classifyAllHaskellInputs = map InputPath -partitionInputs :: [NormalizedFilePath] -> PartitionedInputs -partitionInputs = foldr classifyInputPath emptyInputs +-- Dependency files should not be considered +-- ProjectHaskellFiles, so we filter them out +-- before classifying all other files as +-- ProjectHaskellFiles. +classifyProjectHaskellInputs :: [NormalizedFilePath] -> [InputPath ProjectHaskellFiles] +classifyProjectHaskellInputs = foldr classifyInputPath [] where - classifyInputPath :: NormalizedFilePath -> PartitionedInputs -> PartitionedInputs - classifyInputPath nfp inputs@(PartitionedInputs projectInputs dependencyInputs) = - case [".hls", "dependencies"] `isInfixOf` splitDirectories (fromNormalizedFilePath nfp) of - True -> inputs{ projectFiles = InputPath nfp : projectInputs } - False -> inputs{ dependencyFiles = InputPath nfp : dependencyInputs } - emptyInputs = PartitionedInputs [] [] \ No newline at end of file + classifyInputPath :: NormalizedFilePath -> [InputPath ProjectHaskellFiles] -> [InputPath ProjectHaskellFiles] + classifyInputPath nfp projectInputs = + case dependencyDirectory `isInfixOf` rawInput of + -- The input is a dependency, so don't include + -- it in the project inputs. + True -> projectInputs + -- The input is not a depencency, so include it + -- in the project inputs + False -> InputPath nfp : projectInputs + where + dependencyDirectory :: [FilePath] + dependencyDirectory = [".hls", "dependencies"] + rawInput :: [FilePath] + rawInput = splitDirectories (fromNormalizedFilePath nfp) diff --git a/ghcide/src/Development/IDE/Core/RuleTypes.hs b/ghcide/src/Development/IDE/Core/RuleTypes.hs index cbc72ecb7e..8d9f9d6e4f 100644 --- a/ghcide/src/Development/IDE/Core/RuleTypes.hs +++ b/ghcide/src/Development/IDE/Core/RuleTypes.hs @@ -23,13 +23,13 @@ import Data.Hashable import qualified Data.Map as M import Data.Time.Clock.POSIX import Data.Typeable +import Development.IDE.Core.InputPath import Development.IDE.GHC.Compat hiding (HieFileResult) import Development.IDE.GHC.Compat.Util import Development.IDE.GHC.CoreFile import Development.IDE.GHC.Util import Development.IDE.Graph -import Development.IDE.Graph.Internal.RuleInput (Input(ProjectHaskellFile, DependencyHaskellFile, NoFile), RuleInput, ValidInputs(ProjectHaskellFilesOnly, AllHaskellFiles, NoFiles)) import Development.IDE.Import.DependencyInformation import Development.IDE.Types.HscEnvEq (HscEnvEq) import Development.IDE.Types.KnownTargets @@ -66,26 +66,26 @@ encodeLinkableType (Just ObjectLinkable) = "2" -- | The parse tree for the file using GetFileContents type instance RuleResult GetParsedModule = ParsedModule -type instance RuleInput GetParsedModule = AllHaskellFiles +type instance RuleInput GetParsedModule = InputPath AllHaskellFiles -- | The parse tree for the file using GetFileContents, -- all comments included using Opt_KeepRawTokenStream type instance RuleResult GetParsedModuleWithComments = ParsedModule -type instance RuleInput GetParsedModuleWithComments = AllHaskellFiles +type instance RuleInput GetParsedModuleWithComments = InputPath AllHaskellFiles type instance RuleResult GetModuleGraph = DependencyInformation -type instance RuleInput GetModuleGraph = NoFiles +type instance RuleInput GetModuleGraph = () data GetKnownTargets = GetKnownTargets deriving (Show, Generic, Eq, Ord) instance Hashable GetKnownTargets instance NFData GetKnownTargets type instance RuleResult GetKnownTargets = KnownTargets -type instance RuleInput GetKnownTargets = NoFiles +type instance RuleInput GetKnownTargets = () -- | Convert to Core, requires TypeCheck* type instance RuleResult GenerateCore = ModGuts -type instance RuleInput GenerateCore = ProjectHaskellFilesOnly +type instance RuleInput GenerateCore = InputPath ProjectHaskellFiles data GenerateCore = GenerateCore deriving (Eq, Show, Typeable, Generic) @@ -93,7 +93,7 @@ instance Hashable GenerateCore instance NFData GenerateCore type instance RuleResult GetLinkable = LinkableResult -type instance RuleInput GetLinkable = ProjectHaskellFilesOnly +type instance RuleInput GetLinkable = InputPath ProjectHaskellFiles data LinkableResult = LinkableResult @@ -119,7 +119,7 @@ instance Hashable GetImportMap instance NFData GetImportMap type instance RuleResult GetImportMap = ImportMap -type instance RuleInput GetImportMap = ProjectHaskellFilesOnly +type instance RuleInput GetImportMap = InputPath ProjectHaskellFiles newtype ImportMap = ImportMap { importMap :: M.Map ModuleName NormalizedFilePath -- ^ Where are the modules imported by this file located? } deriving stock Show @@ -240,15 +240,15 @@ instance Show HieAstResult where -- | The type checked version of this file, requires TypeCheck+ type instance RuleResult TypeCheck = TcModuleResult -type instance RuleInput TypeCheck = ProjectHaskellFilesOnly +type instance RuleInput TypeCheck = InputPath ProjectHaskellFiles -- | The uncompressed HieAST type instance RuleResult GetHieAst = HieAstResult -type instance RuleInput GetHieAst = AllHaskellFiles +type instance RuleInput GetHieAst = InputPath AllHaskellFiles -- | A IntervalMap telling us what is in scope at each point type instance RuleResult GetBindings = Bindings -type instance RuleInput GetBindings = ProjectHaskellFilesOnly +type instance RuleInput GetBindings = InputPath ProjectHaskellFiles data DocAndTyThingMap = DKMap {getDocMap :: !DocMap, getTyThingMap :: !TyThingMap} instance NFData DocAndTyThingMap where @@ -258,50 +258,50 @@ instance Show DocAndTyThingMap where show = const "docmap" type instance RuleResult GetDocMap = DocAndTyThingMap -type instance RuleInput GetDocMap = ProjectHaskellFilesOnly +type instance RuleInput GetDocMap = InputPath ProjectHaskellFiles -- | A GHC session that we reuse. type instance RuleResult GhcSession = HscEnvEq -type instance RuleInput GhcSession = ProjectHaskellFilesOnly +type instance RuleInput GhcSession = InputPath ProjectHaskellFiles -- | A GHC session preloaded with all the dependencies -- This rule is also responsible for calling ReportImportCycles for the direct dependencies type instance RuleResult GhcSessionDeps = HscEnvEq -type instance RuleInput GhcSessionDeps = ProjectHaskellFilesOnly +type instance RuleInput GhcSessionDeps = InputPath ProjectHaskellFiles -- | Resolve the imports in a module to the file path of a module in the same package type instance RuleResult GetLocatedImports = [(Located ModuleName, Maybe ArtifactsLocation)] -type instance RuleInput GetLocatedImports = ProjectHaskellFilesOnly +type instance RuleInput GetLocatedImports = InputPath ProjectHaskellFiles -- | This rule is used to report import cycles. It depends on GetModuleGraph. -- We cannot report the cycles directly from GetModuleGraph since -- we can only report diagnostics for the current file. type instance RuleResult ReportImportCycles = () -type instance RuleInput ReportImportCycles = ProjectHaskellFilesOnly +type instance RuleInput ReportImportCycles = InputPath ProjectHaskellFiles -- | Read the module interface file from disk. Throws an error for VFS files. -- This is an internal rule, use 'GetModIface' instead. type instance RuleResult GetModIfaceFromDisk = HiFileResult -type instance RuleInput GetModIfaceFromDisk = ProjectHaskellFilesOnly +type instance RuleInput GetModIfaceFromDisk = InputPath ProjectHaskellFiles -- | GetModIfaceFromDisk and index the `.hie` file into the database. -- This is an internal rule, use 'GetModIface' instead. type instance RuleResult GetModIfaceFromDiskAndIndex = HiFileResult -type instance RuleInput GetModIfaceFromDiskAndIndex = ProjectHaskellFilesOnly +type instance RuleInput GetModIfaceFromDiskAndIndex = InputPath ProjectHaskellFiles -- | Get a module interface details, either from an interface file or a typechecked module type instance RuleResult GetModIface = HiFileResult -type instance RuleInput GetModIface = AllHaskellFiles +type instance RuleInput GetModIface = InputPath AllHaskellFiles -- | Get the contents of a file, either dirty (if the buffer is modified) or Nothing to mean use from disk. type instance RuleResult GetFileContents = (FileVersion, Maybe Text) -type instance RuleInput GetFileContents = AllHaskellFiles +type instance RuleInput GetFileContents = InputPath AllHaskellFiles type instance RuleResult GetFileExists = Bool -type instance RuleInput GetFileExists = AllHaskellFiles +type instance RuleInput GetFileExists = InputPath AllHaskellFiles type instance RuleResult AddWatchedFile = Bool -type instance RuleInput AddWatchedFile = AllHaskellFiles +type instance RuleInput AddWatchedFile = InputPath AllHaskellFiles -- The Shake key type for getModificationTime queries @@ -331,7 +331,7 @@ pattern GetModificationTime = GetModificationTime_ {missingFileDiagnostics=True} -- | Get the modification time of a file. type instance RuleResult GetModificationTime = FileVersion -type instance RuleInput GetModificationTime = AllHaskellFiles +type instance RuleInput GetModificationTime = InputPath AllHaskellFiles -- | Either the mtime from disk or an LSP version -- LSP versions always compare as greater than on disk versions @@ -374,7 +374,7 @@ instance Hashable IsFileOfInterestResult instance NFData IsFileOfInterestResult type instance RuleResult IsFileOfInterest = IsFileOfInterestResult -type instance RuleInput IsFileOfInterest = AllHaskellFiles +type instance RuleInput IsFileOfInterest = InputPath AllHaskellFiles data ModSummaryResult = ModSummaryResult { msrModSummary :: !ModSummary @@ -397,11 +397,11 @@ instance NFData ModSummaryResult where -- | Generate a ModSummary that has enough information to be used to get .hi and .hie files. -- without needing to parse the entire source type instance RuleResult GetModSummary = ModSummaryResult -type instance RuleInput GetModSummary = AllHaskellFiles +type instance RuleInput GetModSummary = InputPath AllHaskellFiles -- | Generate a ModSummary with the timestamps and preprocessed content elided, for more successful early cutoff type instance RuleResult GetModSummaryWithoutTimestamps = ModSummaryResult -type instance RuleInput GetModSummaryWithoutTimestamps = AllHaskellFiles +type instance RuleInput GetModSummaryWithoutTimestamps = InputPath AllHaskellFiles data GetParsedModule = GetParsedModule deriving (Eq, Show, Typeable, Generic) @@ -420,7 +420,7 @@ instance NFData GetLocatedImports -- | Does this module need to be compiled? type instance RuleResult NeedsCompilation = Maybe LinkableType -type instance RuleInput NeedsCompilation = ProjectHaskellFilesOnly +type instance RuleInput NeedsCompilation = InputPath ProjectHaskellFiles data NeedsCompilation = NeedsCompilation deriving (Eq, Show, Typeable, Generic) @@ -514,7 +514,7 @@ instance Hashable GetClientSettings instance NFData GetClientSettings type instance RuleResult GetClientSettings = Hashed (Maybe Value) -type instance RuleInput GetClientSettings = NoFiles +type instance RuleInput GetClientSettings = () data AddWatchedFile = AddWatchedFile deriving (Eq, Show, Typeable, Generic) instance Hashable AddWatchedFile @@ -525,7 +525,7 @@ instance NFData AddWatchedFile -- thread killed exception issues, so we lift it to a full rule. -- https://github.com/digital-asset/daml/pull/2808#issuecomment-529639547 type instance RuleResult GhcSessionIO = IdeGhcSession -type instance RuleInput GhcSessionIO = ProjectHaskellFilesOnly +type instance RuleInput GhcSessionIO = InputPath ProjectHaskellFiles data IdeGhcSession = IdeGhcSession { loadSessionFun :: FilePath -> IO (IdeResult HscEnvEq, [FilePath]) diff --git a/ghcide/src/Development/IDE/Core/Shake.hs b/ghcide/src/Development/IDE/Core/Shake.hs index da88a8c5ef..bc1eec1cc5 100644 --- a/ghcide/src/Development/IDE/Core/Shake.hs +++ b/ghcide/src/Development/IDE/Core/Shake.hs @@ -122,7 +122,7 @@ import Data.Vector (Vector) import qualified Data.Vector as Vector import Development.IDE.Core.Debouncer import Development.IDE.Core.FileUtils (getModTime) -import Development.IDE.Core.InputPath (InputPath (unInputPath, InputPath)) +import Development.IDE.Core.InputPath import Development.IDE.Core.PositionMapping import Development.IDE.Core.ProgressReporting import Development.IDE.Core.RuleTypes @@ -181,8 +181,7 @@ import System.FilePath hiding (makeRelative) import System.IO.Unsafe (unsafePerformIO) import System.Time.Extra import UnliftIO (MonadUnliftIO (withRunInIO)) -import Development.IDE.Graph.Internal.RuleInput (RuleInput, HasInput, Input(NoFile)) - +import Development.IDE.Core.InputPath (HasNormalizedFilePath(getNormalizedFilePath)) data Log = LogCreateHieDbExportsMapStart @@ -387,7 +386,7 @@ getPluginConfigAction plId = do -- This is called when we don't already have a result, or computing the rule failed. -- The result of this function will always be marked as 'stale', and a 'proper' rebuild of the rule will -- be queued if the rule hasn't run before. -addPersistentRule :: IdeRule k i is v => k -> (NormalizedFilePath -> IdeAction (Maybe (v,PositionDelta,Maybe Int32))) -> Rules () +addPersistentRule :: IdeRule k i v => k -> (NormalizedFilePath -> IdeAction (Maybe (v,PositionDelta,Maybe Int32))) -> Rules () addPersistentRule k getVal = do ShakeExtras{persistentKeys} <- getShakeExtrasRules void $ liftIO $ atomically $ modifyTVar' persistentKeys $ insertKeyMap (newKey k) (fmap (fmap (first3 toDyn)) . getVal) @@ -455,8 +454,8 @@ getIdeOptionsIO ide = do -- | Return the most recent, potentially stale, value and a PositionMapping -- for the version of that value. -lastValueIO :: IdeRule k i is v => ShakeExtras -> k -> InputPath i -> IO (Maybe (v, PositionMapping)) -lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k (InputPath file) = do +lastValueIO :: IdeRule k i v => ShakeExtras -> k -> i -> IO (Maybe (v, PositionMapping)) +lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k input = do let readPersistent | IdeTesting testing <- ideTesting s -- Don't read stale persistent values in tests @@ -466,20 +465,20 @@ lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k (InputPath fil mv <- runMaybeT $ do liftIO $ logWith (shakeRecorder s) Debug $ LogLookupPersistentKey (T.pack $ show k) f <- MaybeT $ pure $ lookupKeyMap (newKey k) pmap - (dv,del,ver) <- MaybeT $ runIdeAction "lastValueIO" s $ f file + (dv,del,ver) <- MaybeT $ runIdeAction "lastValueIO" s $ f $ getNormalizedFilePath input MaybeT $ pure $ (,del,ver) <$> fromDynamic dv case mv of Nothing -> atomicallyNamed "lastValueIO 1" $ do - STM.focus (Focus.alter (alterValue $ Failed True)) (toKey k file) state + STM.focus (Focus.alter (alterValue $ Failed True)) (toKey k $ getNormalizedFilePath input) state return Nothing Just (v,del,mbVer) -> do actual_version <- case mbVer of Just ver -> pure (Just $ VFSVersion ver) - Nothing -> (Just . ModificationTime <$> getModTime (fromNormalizedFilePath file)) + Nothing -> (Just . ModificationTime <$> getModTime (fromNormalizedFilePath $ getNormalizedFilePath input)) `catch` (\(_ :: IOException) -> pure Nothing) atomicallyNamed "lastValueIO 2" $ do - STM.focus (Focus.alter (alterValue $ Stale (Just del) actual_version (toDyn v))) (toKey k file) state - Just . (v,) . addOldDelta del <$> mappingForVersion positionMapping file actual_version + STM.focus (Focus.alter (alterValue $ Stale (Just del) actual_version (toDyn v))) (toKey k input) state + Just . (v,) . addOldDelta del <$> mappingForVersion positionMapping (getNormalizedFilePath input) actual_version -- We got a new stale value from the persistent rule, insert it in the map without affecting diagnostics alterValue new Nothing = Just (ValueWithDiagnostics new mempty) -- If it wasn't in the map, give it empty diagnostics @@ -489,19 +488,19 @@ lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k (InputPath fil -- Something already succeeded before, leave it alone _ -> old - atomicallyNamed "lastValueIO 4" (STM.lookup (toKey k file) state) >>= \case + atomicallyNamed "lastValueIO 4" (STM.lookup (toKey k input) state) >>= \case Nothing -> readPersistent Just (ValueWithDiagnostics value _) -> case value of Succeeded ver (fromDynamic -> Just v) -> - atomicallyNamed "lastValueIO 5" $ Just . (v,) <$> mappingForVersion positionMapping file ver + atomicallyNamed "lastValueIO 5" $ Just . (v,) <$> mappingForVersion positionMapping (getNormalizedFilePath input) ver Stale del ver (fromDynamic -> Just v) -> - atomicallyNamed "lastValueIO 6" $ Just . (v,) . maybe id addOldDelta del <$> mappingForVersion positionMapping file ver + atomicallyNamed "lastValueIO 6" $ Just . (v,) . maybe id addOldDelta del <$> mappingForVersion positionMapping (getNormalizedFilePath input) ver Failed p | not p -> readPersistent _ -> pure Nothing -- | Return the most recent, potentially stale, value and a PositionMapping -- for the version of that value. -lastValue :: IdeRule k i is v => k -> InputPath i -> Action (Maybe (v, PositionMapping)) +lastValue :: IdeRule k i v => k -> i -> Action (Maybe (v, PositionMapping)) lastValue key file = do s <- getShakeExtras liftIO $ lastValueIO s key file @@ -516,15 +515,12 @@ mappingForVersion allMappings file (Just (VFSVersion ver)) = do return $ maybe zeroMapping snd $ EM.lookup ver =<< mapping mappingForVersion _ _ _ = pure zeroMapping -type IdeRule k i is v = - ( IdeValueRule k v - , RuleInput k ~ is - , HasInput i is - ) - -type IdeValueRule k v = - ( Shake.RuleResult k ~ v +type IdeRule k i v = + ( Shake.RuleInput k ~ i + , Shake.RuleResult k ~ v , Shake.ShakeValue k + , Shake.ShakeValue i + , HasNormalizedFilePath i , Show v , Typeable v , NFData v @@ -590,15 +586,15 @@ shakeDatabaseProfileIO mbProfileDir = do shakeProfileDatabase shakeDb $ dir file return (dir file) -setValues :: IdeRule k i is v +setValues :: IdeRule k i v => Values -> k - -> InputPath i + -> i -> Value v -> Vector FileDiagnostic -> STM () -setValues state key (InputPath file) val diags = - STM.insert (ValueWithDiagnostics (fmap toDyn val) diags) (toKey key file) state +setValues state key input val diags = + STM.insert (ValueWithDiagnostics (fmap toDyn val) diags) (toKey key $ getNormalizedFilePath input) state -- | Delete the value stored for a given ide build key @@ -616,14 +612,14 @@ deleteValue ShakeExtras{state} key file = do -- | We return Nothing if the rule has not run and Just Failed if it has failed to produce a value. getValues :: - forall k i is v. - IdeRule k i is v => + forall k i v. + IdeRule k i v => Values -> k -> - InputPath i -> + i -> STM (Maybe (Value v, Vector FileDiagnostic)) -getValues state key (InputPath file) = do - STM.lookup (toKey key file) state >>= \case +getValues state key input = do + STM.lookup (toKey key $ getNormalizedFilePath input) state >>= \case Nothing -> pure Nothing Just (ValueWithDiagnostics v diagsV) -> do let !r = seqValue $ fmap (fromJust . fromDynamic @v) v @@ -1019,23 +1015,23 @@ preservedKeys checkParents = HSet.fromList $ -- | Define a new Rule without early cutoff define - :: IdeRule k i is v - => Recorder (WithPriority Log) -> (k -> InputPath i -> Action (IdeResult v)) -> Rules () + :: IdeRule k i v + => Recorder (WithPriority Log) -> (k -> i -> Action (IdeResult v)) -> Rules () define recorder op = defineEarlyCutoff recorder $ Rule $ \k v -> (Nothing,) <$> op k v defineNoDiagnostics - :: IdeRule k i is v - => Recorder (WithPriority Log) -> (k -> InputPath i -> Action (Maybe v)) -> Rules () + :: IdeRule k i v + => Recorder (WithPriority Log) -> (k -> i -> Action (Maybe v)) -> Rules () defineNoDiagnostics recorder op = defineEarlyCutoff recorder $ RuleNoDiagnostics $ \k v -> (Nothing,) <$> op k v -- | Request a Rule result if available -use :: IdeRule k i is v - => k -> InputPath i -> Action (Maybe v) -use key file = runIdentity <$> uses key (Identity file) +use :: IdeRule k i v + => k -> i -> Action (Maybe v) +use key input = runIdentity <$> uses key (Identity input) -- | Request a Rule result, it not available return the last computed result, if any, which may be stale -useWithStale :: IdeRule k i is v - => k -> InputPath i -> Action (Maybe (v, PositionMapping)) +useWithStale :: IdeRule k i v + => k -> i -> Action (Maybe (v, PositionMapping)) useWithStale key file = runIdentity <$> usesWithStale key (Identity file) -- |Request a Rule result, it not available return the last computed result @@ -1045,9 +1041,9 @@ useWithStale key file = runIdentity <$> usesWithStale key (Identity file) -- none available. -- -- WARNING: Not suitable for PluginHandlers. Use `useWithStaleE` instead. -useWithStale_ :: IdeRule k i is v - => k -> InputPath i -> Action (v, PositionMapping) -useWithStale_ key file = runIdentity <$> usesWithStale_ key (Identity file) +useWithStale_ :: IdeRule k i v + => k -> i -> Action (v, PositionMapping) +useWithStale_ key input = runIdentity <$> usesWithStale_ key (Identity input) -- |Plural version of 'useWithStale_' -- @@ -1055,9 +1051,9 @@ useWithStale_ key file = runIdentity <$> usesWithStale_ key (Identity file) -- none available. -- -- WARNING: Not suitable for PluginHandlers. -usesWithStale_ :: (Traversable f, IdeRule k i is v) => k -> f (InputPath i) -> Action (f (v, PositionMapping)) -usesWithStale_ key files = do - res <- usesWithStale key files +usesWithStale_ :: (Traversable f, IdeRule k i v) => k -> f i -> Action (f (v, PositionMapping)) +usesWithStale_ key inputs = do + res <- usesWithStale key inputs case sequence res of Nothing -> liftIO $ throwIO $ BadDependency (show key) Just v -> return v @@ -1086,27 +1082,27 @@ data FastResult a = FastResult { stale :: Maybe (a,PositionMapping), uptoDate :: -- | Lookup value in the database and return with the stale value immediately -- Will queue an action to refresh the value. -- Might block the first time the rule runs, but never blocks after that. -useWithStaleFast :: IdeRule k i is v => k -> InputPath i -> IdeAction (Maybe (v, PositionMapping)) -useWithStaleFast key file = stale <$> useWithStaleFast' key file +useWithStaleFast :: IdeRule k i v => k -> i -> IdeAction (Maybe (v, PositionMapping)) +useWithStaleFast key input = stale <$> useWithStaleFast' key input -- | Same as useWithStaleFast but lets you wait for an up to date result -useWithStaleFast' :: IdeRule k i is v => k -> InputPath i -> IdeAction (FastResult v) -useWithStaleFast' key file = do +useWithStaleFast' :: IdeRule k i v => k -> i -> IdeAction (FastResult v) +useWithStaleFast' key input = do -- This lookup directly looks up the key in the shake database and -- returns the last value that was computed for this key without -- checking freshness. -- Async trigger the key to be built anyway because we want to -- keep updating the value in the key. - waitValue <- delayedAction $ mkDelayedAction ("C:" ++ show key ++ ":" ++ fromNormalizedFilePath (unInputPath file)) Debug $ use key file + waitValue <- delayedAction $ mkDelayedAction ("C:" ++ show key ++ ":" ++ fromNormalizedFilePath (getNormalizedFilePath input)) Debug $ use key input s@ShakeExtras{state} <- askShake - r <- liftIO $ atomicallyNamed "useStateFast" $ getValues state key file + r <- liftIO $ atomicallyNamed "useStateFast" $ getValues state key input liftIO $ case r of -- block for the result if we haven't computed before Nothing -> do -- Check if we can get a stale value from disk - res <- lastValueIO s key file + res <- lastValueIO s key input case res of Nothing -> do a <- waitValue @@ -1114,11 +1110,11 @@ useWithStaleFast' key file = do Just _ -> pure $ FastResult res waitValue -- Otherwise, use the computed value even if it's out of date. Just _ -> do - res <- lastValueIO s key file + res <- lastValueIO s key input pure $ FastResult res waitValue -useNoFile :: forall k is v. IdeRule k NoFile is v => k -> Action (Maybe v) -useNoFile key = use key (InputPath @NoFile emptyFilePath) +useNoFile :: forall k is v. IdeRule k () v => k -> Action (Maybe v) +useNoFile key = use key () -- Requests a rule if available. -- @@ -1126,11 +1122,11 @@ useNoFile key = use key (InputPath @NoFile emptyFilePath) -- none available. -- -- WARNING: Not suitable for PluginHandlers. Use `useE` instead. -use_ :: IdeRule k i is v => k -> InputPath i -> Action v -use_ key file = runIdentity <$> uses_ key (Identity file) +use_ :: IdeRule k i v => k -> i -> Action v +use_ key input = runIdentity <$> uses_ key (Identity input) -useNoFile_ :: forall k is v. IdeRule k NoFile is v => k -> Action v -useNoFile_ key = use_ key (InputPath @NoFile emptyFilePath) +useNoFile_ :: forall k is v. IdeRule k () v => k -> Action v +useNoFile_ key = use_ key () -- |Plural version of `use_` -- @@ -1138,7 +1134,7 @@ useNoFile_ key = use_ key (InputPath @NoFile emptyFilePath) -- none available. -- -- WARNING: Not suitable for PluginHandlers. Use `usesE` instead. -uses_ :: (Traversable f, IdeRule k i is v) => k -> f (InputPath i) -> Action (f v) +uses_ :: (Traversable f, IdeRule k i v) => k -> f i -> Action (f v) uses_ key files = do res <- uses key files case sequence res of @@ -1146,102 +1142,102 @@ uses_ key files = do Just v -> return v -- | Plural version of 'use' -uses :: (Traversable f, IdeRule k i is v) - => k -> f (InputPath i) -> Action (f (Maybe v)) -uses key files = fmap (\(A value) -> currentValue value) <$> apply (fmap (Q . (key,) . unInputPath) files) +uses :: (Traversable f, IdeRule k i v) + => k -> f i -> Action (f (Maybe v)) +uses key files = fmap (\(A value) -> currentValue value) <$> apply (fmap (Q . (key,) . getNormalizedFilePath) files) -- | Return the last computed result which might be stale. -usesWithStale :: (Traversable f, IdeRule k i is v) - => k -> f (InputPath i) -> Action (f (Maybe (v, PositionMapping))) -usesWithStale key files = do - _ <- apply (fmap (Q . (key,) . unInputPath) files) +usesWithStale :: (Traversable f, IdeRule k i v) + => k -> f i -> Action (f (Maybe (v, PositionMapping))) +usesWithStale key inputs = do + _ <- apply (fmap (Q . (key,) . getNormalizedFilePath) inputs) -- We don't look at the result of the 'apply' since 'lastValue' will -- return the most recent successfully computed value regardless of -- whether the rule succeeded or not. - traverse (lastValue key) files + traverse (lastValue key) inputs -useWithoutDependency :: IdeValueRule k v - => k -> NormalizedFilePath -> Action (Maybe v) -useWithoutDependency key file = - (\(Identity (A value)) -> currentValue value) <$> applyWithoutDependency (Identity (Q (key, file))) +useWithoutDependency :: IdeRule k i v + => k -> i -> Action (Maybe v) +useWithoutDependency key input = + (\(Identity (A value)) -> currentValue value) <$> applyWithoutDependency (Identity (Q (key, getNormalizedFilePath input))) data RuleBody k i v - = Rule (k -> InputPath i -> Action (Maybe BS.ByteString, IdeResult v)) - | RuleNoDiagnostics (k -> InputPath i -> Action (Maybe BS.ByteString, Maybe v)) + = Rule (k -> i -> Action (Maybe BS.ByteString, IdeResult v)) + | RuleNoDiagnostics (k -> i -> Action (Maybe BS.ByteString, Maybe v)) | RuleWithCustomNewnessCheck { newnessCheck :: BS.ByteString -> BS.ByteString -> Bool - , build :: k -> InputPath i -> Action (Maybe BS.ByteString, Maybe v) + , build :: k -> i -> Action (Maybe BS.ByteString, Maybe v) } - | RuleWithOldValue (k -> InputPath i -> Value v -> Action (Maybe BS.ByteString, IdeResult v)) + | RuleWithOldValue (k -> i -> Value v -> Action (Maybe BS.ByteString, IdeResult v)) -- | Define a new Rule with early cutoff defineEarlyCutoff - :: forall k i is v - . IdeRule k i is v + :: forall k i v + . IdeRule k i v => Recorder (WithPriority Log) -> RuleBody k i v -> Rules () -defineEarlyCutoff recorder (Rule op) = addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> otTracedAction key file mode traceA $ \traceDiagnostics -> do +defineEarlyCutoff recorder (Rule op) = addRule $ \(Q (key, input)) (old :: Maybe BS.ByteString) mode -> otTracedAction key (getNormalizedFilePath input) mode traceA $ \traceDiagnostics -> do extras <- getShakeExtras let diagnostics ver diags = do traceDiagnostics diags - updateFileDiagnostics recorder file ver (newKey key) extras . map (\(_,y,z) -> (y,z)) $ diags - defineEarlyCutoff' diagnostics (==) key (InputPath @i file) old mode $ const $ op key (InputPath @i file) -defineEarlyCutoff recorder (RuleNoDiagnostics op) = addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> otTracedAction key file mode traceA $ \traceDiagnostics -> do + updateFileDiagnostics recorder (getNormalizedFilePath input) ver (newKey key) extras . map (\(_,y,z) -> (y,z)) $ diags + defineEarlyCutoff' diagnostics (==) key input old mode $ const $ op key input +defineEarlyCutoff recorder (RuleNoDiagnostics op) = addRule $ \(Q (key, input)) (old :: Maybe BS.ByteString) mode -> otTracedAction key (getNormalizedFilePath input) mode traceA $ \traceDiagnostics -> do let diagnostics _ver diags = do traceDiagnostics diags mapM_ (logWith recorder Warning . LogDefineEarlyCutoffRuleNoDiagHasDiag) diags - defineEarlyCutoff' diagnostics (==) key (InputPath @i file) old mode $ const $ second (mempty,) <$> op key (InputPath @i file) + defineEarlyCutoff' diagnostics (==) key input old mode $ const $ second (mempty,) <$> op key input defineEarlyCutoff recorder RuleWithCustomNewnessCheck{..} = - addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> - otTracedAction key file mode traceA $ \ traceDiagnostics -> do + addRule $ \(Q (key, input)) (old :: Maybe BS.ByteString) mode -> + otTracedAction key (getNormalizedFilePath input) mode traceA $ \ traceDiagnostics -> do let diagnostics _ver diags = do traceDiagnostics diags mapM_ (logWith recorder Warning . LogDefineEarlyCutoffRuleCustomNewnessHasDiag) diags - defineEarlyCutoff' diagnostics newnessCheck key (InputPath @i file) old mode $ - const $ second (mempty,) <$> build key (InputPath @i file) -defineEarlyCutoff recorder (RuleWithOldValue op) = addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> otTracedAction key file mode traceA $ \traceDiagnostics -> do + defineEarlyCutoff' diagnostics newnessCheck key input old mode $ + const $ second (mempty,) <$> build key input +defineEarlyCutoff recorder (RuleWithOldValue op) = addRule $ \(Q (key, input)) (old :: Maybe BS.ByteString) mode -> otTracedAction key (getNormalizedFilePath input) mode traceA $ \traceDiagnostics -> do extras <- getShakeExtras let diagnostics ver diags = do traceDiagnostics diags - updateFileDiagnostics recorder file ver (newKey key) extras . map (\(_,y,z) -> (y,z)) $ diags - defineEarlyCutoff' diagnostics (==) key (InputPath @i file) old mode $ op key (InputPath @i file) + updateFileDiagnostics recorder (getNormalizedFilePath input) ver (newKey key) extras . map (\(_,y,z) -> (y,z)) $ diags + defineEarlyCutoff' diagnostics (==) key input old mode $ op key input -defineNoFile :: forall k i is v. IdeRule k i is v => Recorder (WithPriority Log) -> (k -> Action v) -> Rules () -defineNoFile recorder f = defineNoDiagnostics recorder $ \k file -> do - if file == (InputPath @i emptyFilePath) then do res <- f k; return (Just res) else - fail $ "Rule " ++ show k ++ " should always be called with the empty string for a file" +defineNoFile :: forall k v. IdeRule k () v => Recorder (WithPriority Log) -> (k -> Action v) -> Rules () +defineNoFile recorder f = defineNoDiagnostics recorder $ \k _ -> do + res <- f k + return (Just res) -defineEarlyCutOffNoFile :: forall k i is v. IdeRule k i is v => Recorder (WithPriority Log) -> (k -> Action (BS.ByteString, v)) -> Rules () -defineEarlyCutOffNoFile recorder f = defineEarlyCutoff recorder $ RuleNoDiagnostics $ \k file -> do - if file == (InputPath @i emptyFilePath) then do (hashString, res) <- f k; return (Just hashString, Just res) else - fail $ "Rule " ++ show k ++ " should always be called with the empty string for a file" +defineEarlyCutOffNoFile :: forall k v. IdeRule k () v => Recorder (WithPriority Log) -> (k -> Action (BS.ByteString, v)) -> Rules () +defineEarlyCutOffNoFile recorder f = defineEarlyCutoff recorder $ RuleNoDiagnostics $ \k _ -> do + (hashString, res) <- f k + return (Just hashString, Just res) defineEarlyCutoff' - :: forall k i is v. IdeRule k i is v + :: forall k i v. IdeRule k i v => (Maybe Int32 -> [FileDiagnostic] -> Action ()) -- ^ update diagnostics -- | compare current and previous for freshness -> (BS.ByteString -> BS.ByteString -> Bool) -> k - -> InputPath i + -> i -> Maybe BS.ByteString -> RunMode -> (Value v -> Action (Maybe BS.ByteString, IdeResult v)) -> Action (RunResult (A (RuleResult k))) -defineEarlyCutoff' doDiagnostics cmp key file mbOld mode action = do - let rawFile = unInputPath file +defineEarlyCutoff' doDiagnostics cmp key input mbOld mode action = do + let rawFile = getNormalizedFilePath input ShakeExtras{state, progress, dirtyKeys} <- getShakeExtras options <- getIdeOptions let trans g x = withRunInIO $ \run -> g (run x) (if optSkipProgress options key then id else trans (inProgress progress rawFile)) $ do val <- case mbOld of Just old | mode == RunDependenciesSame -> do - mbValue <- liftIO $ atomicallyNamed "define - read 1" $ getValues state key file + mbValue <- liftIO $ atomicallyNamed "define - read 1" $ getValues state key input case mbValue of -- No changes in the dependencies and we have -- an existing successful result. Just (v@(Succeeded _ x), diags) -> do - ver <- estimateFileVersionUnsafely key (Just x) file + ver <- estimateFileVersionUnsafely key (Just x) input doDiagnostics (vfsVersion =<< ver) $ Vector.toList diags return $ Just $ RunResult ChangedNothing old (A v) $ return () _ -> return Nothing @@ -1252,7 +1248,7 @@ defineEarlyCutoff' doDiagnostics cmp key file mbOld mode action = do res <- case val of Just res -> return res Nothing -> do - staleV <- liftIO $ atomicallyNamed "define -read 3" $ getValues state key file <&> \case + staleV <- liftIO $ atomicallyNamed "define -read 3" $ getValues state key input <&> \case Nothing -> Failed False Just (Succeeded ver v, _) -> Stale Nothing ver v Just (Stale d ver v, _) -> Stale d ver v @@ -1262,7 +1258,7 @@ defineEarlyCutoff' doDiagnostics cmp key file mbOld mode action = do \(e :: SomeException) -> do pure (Nothing, ([ideErrorText rawFile $ T.pack $ show e | not $ isBadDependency e],Nothing)) - ver <- estimateFileVersionUnsafely key mbRes file + ver <- estimateFileVersionUnsafely key mbRes input (bs, res) <- case mbRes of Nothing -> do pure (toShakeValue ShakeStale mbBs, staleV) @@ -1280,8 +1276,8 @@ defineEarlyCutoff' doDiagnostics cmp key file mbOld mode action = do (A res) $ do -- this hook needs to be run in the same transaction as the key is marked clean -- see Note [Housekeeping rule cache and dirty key outside of hls-graph] - setValues state key file res (Vector.fromList diags) - modifyTVar' dirtyKeys (deleteKeySet $ toKey key rawFile) + setValues state key input res (Vector.fromList diags) + modifyTVar' dirtyKeys (deleteKeySet $ toKey key input) return res where -- Highly unsafe helper to compute the version of a file @@ -1290,10 +1286,10 @@ defineEarlyCutoff' doDiagnostics cmp key file mbOld mode action = do estimateFileVersionUnsafely :: k -> Maybe v - -> InputPath i + -> i -> Action (Maybe FileVersion) - estimateFileVersionUnsafely _k v (InputPath fp) - | fp == emptyFilePath = pure Nothing + estimateFileVersionUnsafely _k v input + | Just Refl <- eqT @i @() = pure Nothing | Just Refl <- eqT @k @GetModificationTime = pure v -- GetModificationTime depends on these rules, so avoid creating a cycle | Just Refl <- eqT @k @AddWatchedFile = pure Nothing @@ -1303,7 +1299,7 @@ defineEarlyCutoff' doDiagnostics cmp key file mbOld mode action = do -- For all other rules - compute the version properly without: -- * creating a dependency: If everything depends on GetModificationTime, we lose early cutoff -- * creating bogus "file does not exists" diagnostics - | otherwise = useWithoutDependency (GetModificationTime_ False) fp + | otherwise = useWithoutDependency (GetModificationTime_ False) (InputPath $ getNormalizedFilePath input) -- Note [Housekeeping rule cache and dirty key outside of hls-graph] -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1468,9 +1464,9 @@ kickSignal testing lspEnv files msg = when testing $ liftIO $ mRunLspT lspEnv $ toJSON $ map fromNormalizedFilePath files -- | Add kick start/done signal to rule -runWithSignal :: (KnownSymbol s0, KnownSymbol s1, IdeRule k i is v) => Proxy s0 -> Proxy s1 -> [InputPath i] -> k -> Action () +runWithSignal :: (KnownSymbol s0, KnownSymbol s1, IdeRule k i v) => Proxy s0 -> Proxy s1 -> [i] -> k -> Action () runWithSignal msgStart msgEnd inputFiles rule = do - let files = map unInputPath inputFiles + let files = map getNormalizedFilePath inputFiles ShakeExtras{ideTesting = Options.IdeTesting testing, lspEnv} <- getShakeExtras kickSignal testing lspEnv files msgStart void $ uses rule inputFiles diff --git a/hls-graph/hls-graph.cabal b/hls-graph/hls-graph.cabal index f9d3ca15ca..d5a9f781de 100644 --- a/hls-graph/hls-graph.cabal +++ b/hls-graph/hls-graph.cabal @@ -60,7 +60,6 @@ library Development.IDE.Graph.Internal.Key Development.IDE.Graph.Internal.Paths Development.IDE.Graph.Internal.Profile - Development.IDE.Graph.Internal.RuleInput Development.IDE.Graph.Internal.Rules Development.IDE.Graph.Internal.Types Development.IDE.Graph.KeyMap diff --git a/hls-graph/src/Development/IDE/Graph.hs b/hls-graph/src/Development/IDE/Graph.hs index 81ad3b3dfd..45f473ec93 100644 --- a/hls-graph/src/Development/IDE/Graph.hs +++ b/hls-graph/src/Development/IDE/Graph.hs @@ -12,7 +12,7 @@ module Development.IDE.Graph( -- * Explicit parallelism parallel, -- * Oracle rules - ShakeValue, RuleResult, + ShakeValue, RuleResult, RuleInput, -- * Special rules alwaysRerun, -- * Actions for inspecting the keys in the database diff --git a/hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs b/hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs deleted file mode 100644 index 9b77f2f777..0000000000 --- a/hls-graph/src/Development/IDE/Graph/Internal/RuleInput.hs +++ /dev/null @@ -1,24 +0,0 @@ -{-# LANGUAGE TypeFamilies #-} -module Development.IDE.Graph.Internal.RuleInput where - -data ValidInputs - = ProjectHaskellFilesOnly - | AllHaskellFiles - | NoFiles - -data Input - = ProjectHaskellFile - | DependencyHaskellFile - | NoFile - -type family RuleInput k :: ValidInputs - -class HasInput (i :: Input) (is :: ValidInputs) - -instance HasInput ProjectHaskellFile ProjectHaskellFilesOnly - -instance HasInput ProjectHaskellFile AllHaskellFiles - -instance HasInput DependencyHaskellFile AllHaskellFiles - -instance HasInput NoFile NoFiles diff --git a/hls-graph/src/Development/IDE/Graph/Internal/Rules.hs b/hls-graph/src/Development/IDE/Graph/Internal/Rules.hs index 9a5f36ca35..00ff8fe641 100644 --- a/hls-graph/src/Development/IDE/Graph/Internal/Rules.hs +++ b/hls-graph/src/Development/IDE/Graph/Internal/Rules.hs @@ -22,6 +22,7 @@ import Development.IDE.Graph.Internal.Types -- | The type mapping between the @key@ or a rule and the resulting @value@. type family RuleResult key -- = value +type family RuleInput k -- input - (InputPath inputclass) or () action :: Action a -> Rules () action x = do From 1e4463f05ea37a340929c71f6703300defc2ee12 Mon Sep 17 00:00:00 2001 From: Elodie Lander Date: Sat, 9 Nov 2024 17:43:27 -0500 Subject: [PATCH 10/14] Use InputPath with InputClass --- ghcide/src/Development/IDE/Core/FileExists.hs | 21 ++- ghcide/src/Development/IDE/Core/FileStore.hs | 41 +++--- ghcide/src/Development/IDE/Core/InputPath.hs | 20 +-- ghcide/src/Development/IDE/Core/OfInterest.hs | 9 +- ghcide/src/Development/IDE/Core/RuleTypes.hs | 58 ++++---- ghcide/src/Development/IDE/Core/Shake.hs | 129 +++++++++--------- .../IDE/Plugin/Completions/Types.hs | 3 + .../Development/IDE/Graph/Internal/Rules.hs | 11 +- 8 files changed, 146 insertions(+), 146 deletions(-) diff --git a/ghcide/src/Development/IDE/Core/FileExists.hs b/ghcide/src/Development/IDE/Core/FileExists.hs index 39533182ae..b2784eb920 100644 --- a/ghcide/src/Development/IDE/Core/FileExists.hs +++ b/ghcide/src/Development/IDE/Core/FileExists.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} @@ -27,7 +26,6 @@ import Development.IDE.Core.RuleTypes import Development.IDE.Core.Shake hiding (Log) import qualified Development.IDE.Core.Shake as Shake import Development.IDE.Graph -import Development.IDE.Graph.Internal.RuleInput import Development.IDE.Types.Location import Development.IDE.Types.Options import Development.IDE.Types.Shake (toKey) @@ -42,6 +40,7 @@ import qualified StmContainers.Map as STM import qualified System.Directory as Dir import qualified System.FilePath.Glob as Glob import Development.IDE.Core.InputPath (InputPath (InputPath)) +import Development.IDE.Graph.Internal.Rules (InputClass(AllHaskellFiles)) {- Note [File existence cache and LSP file watchers] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -136,7 +135,7 @@ fromChange FileChangeType_Changed = Nothing ------------------------------------------------------------------------------------- -- | Returns True if the file exists -getFileExists :: HasInput i AllHaskellFiles => InputPath i -> Action Bool +getFileExists :: InputPath AllHaskellFiles -> Action Bool getFileExists fp = use_ GetFileExists fp {- Note [Which files should we watch?] @@ -170,7 +169,7 @@ allExtensions opts = [extIncBoot | ext <- optExtensions opts, extIncBoot <- [ext -- | Installs the 'getFileExists' rules. -- Provides a fast implementation if client supports dynamic watched files. -- Creates a global state as a side effect in that case. -fileExistsRules :: forall i. HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> Maybe (LanguageContextEnv Config) -> Rules () +fileExistsRules :: Recorder (WithPriority Log) -> Maybe (LanguageContextEnv Config) -> Rules () fileExistsRules recorder lspEnv = do supportsWatchedFiles <- case lspEnv of Nothing -> pure False @@ -192,17 +191,17 @@ fileExistsRules recorder lspEnv = do else const $ pure False if supportsWatchedFiles - then fileExistsRulesFast @i recorder isWatched - else fileExistsRulesSlow @i recorder + then fileExistsRulesFast recorder isWatched + else fileExistsRulesSlow recorder - fileStoreRules @i (cmapWithPrio LogFileStore recorder) (\(InputPath f) -> isWatched f) + fileStoreRules (cmapWithPrio LogFileStore recorder) (\(InputPath f) -> isWatched f) -- Requires an lsp client that provides WatchedFiles notifications, but assumes that this has already been checked. -fileExistsRulesFast :: forall i. HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules () +fileExistsRulesFast :: Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules () fileExistsRulesFast recorder isWatched = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics runGetFileExists where - runGetFileExists :: GetFileExists -> InputPath i -> Action (Maybe BS.ByteString, Maybe Bool) + runGetFileExists :: GetFileExists -> InputPath AllHaskellFiles -> Action (Maybe BS.ByteString, Maybe Bool) runGetFileExists GetFileExists (InputPath file) = do isWF <- isWatched file if isWF @@ -242,11 +241,11 @@ fileExistsFast file = do summarizeExists :: Bool -> Maybe BS.ByteString summarizeExists x = Just $ if x then BS.singleton 1 else BS.empty -fileExistsRulesSlow :: forall i. HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> Rules () +fileExistsRulesSlow :: Recorder (WithPriority Log) -> Rules () fileExistsRulesSlow recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics runGetFileExists where - runGetFileExists :: GetFileExists -> InputPath i -> Action (Maybe BS.ByteString, Maybe Bool) + runGetFileExists :: GetFileExists -> InputPath AllHaskellFiles -> Action (Maybe BS.ByteString, Maybe Bool) runGetFileExists GetFileExists (InputPath file) = fileExistsSlow file fileExistsSlow :: NormalizedFilePath -> Action (Maybe BS.ByteString, Maybe Bool) diff --git a/ghcide/src/Development/IDE/Core/FileStore.hs b/ghcide/src/Development/IDE/Core/FileStore.hs index 31d6a918e1..507d24ec32 100644 --- a/ghcide/src/Development/IDE/Core/FileStore.hs +++ b/ghcide/src/Development/IDE/Core/FileStore.hs @@ -43,7 +43,6 @@ import Development.IDE.Core.Shake hiding (Log) import qualified Development.IDE.Core.Shake as Shake import Development.IDE.GHC.Orphans () import Development.IDE.Graph -import Development.IDE.Graph.Internal.RuleInput import Development.IDE.Import.DependencyInformation import Development.IDE.Types.Diagnostics import Development.IDE.Types.Location @@ -71,7 +70,8 @@ import Language.LSP.VFS import System.FilePath import System.IO.Error import System.IO.Unsafe -import Development.IDE.Core.InputPath (InputPath (unInputPath), partitionInputs, PartitionedInputs (projectFiles, dependencyFiles)) +import Development.IDE.Core.InputPath (InputPath (unInputPath), classifyAllHaskellInputs) +import Development.IDE.Graph.Internal.Rules (InputClass(AllHaskellFiles)) data Log @@ -91,7 +91,7 @@ instance Pretty Log where <+> pretty (fmap (fmap show) reverseDepPaths) LogShake msg -> pretty msg -addWatchedFileRule :: HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> (InputPath i -> Action Bool) -> Rules () +addWatchedFileRule :: Recorder (WithPriority Log) -> (InputPath AllHaskellFiles -> Action Bool) -> Rules () addWatchedFileRule recorder isWatched = defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \AddWatchedFile f -> do isAlreadyWatched <- isWatched f isWp <- isWorkspaceFile $ unInputPath f @@ -104,17 +104,16 @@ addWatchedFileRule recorder isWatched = defineNoDiagnostics (cmapWithPrio LogSha Nothing -> pure $ Just False -getModificationTimeRule :: forall i. HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> Rules () +getModificationTimeRule :: Recorder (WithPriority Log) -> Rules () getModificationTimeRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule runGetModificationTimeImpl where - runGetModificationTimeImpl :: GetModificationTime -> InputPath i -> Action (Maybe BS.ByteString, ([FileDiagnostic], Maybe FileVersion)) - runGetModificationTimeImpl (GetModificationTime_ missingFileDiags) file = - getModificationTimeImpl missingFileDiags file + runGetModificationTimeImpl :: GetModificationTime -> InputPath AllHaskellFiles -> Action (Maybe BS.ByteString, ([FileDiagnostic], Maybe FileVersion)) + runGetModificationTimeImpl (GetModificationTime_ missingFileDiags) input = + getModificationTimeImpl missingFileDiags input getModificationTimeImpl - :: HasInput i AllHaskellFiles - => Bool - -> InputPath i + :: Bool + -> InputPath AllHaskellFiles -> Action (Maybe BS.ByteString, ([FileDiagnostic], Maybe FileVersion)) getModificationTimeImpl missingFileDiags file = do let file' = fromNormalizedFilePath $ unInputPath file @@ -177,15 +176,14 @@ modificationTime :: FileVersion -> Maybe UTCTime modificationTime VFSVersion{} = Nothing modificationTime (ModificationTime posix) = Just $ posixSecondsToUTCTime posix -getFileContentsRule :: forall i. HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> Rules () +getFileContentsRule :: Recorder (WithPriority Log) -> Rules () getFileContentsRule recorder = define (cmapWithPrio LogShake recorder) runGetFileContentsImpl where - runGetFileContentsImpl :: GetFileContents -> InputPath i -> Action ([FileDiagnostic], Maybe (FileVersion, Maybe T.Text)) - runGetFileContentsImpl GetFileContents file = getFileContentsImpl file + runGetFileContentsImpl :: GetFileContents -> InputPath AllHaskellFiles -> Action ([FileDiagnostic], Maybe (FileVersion, Maybe T.Text)) + runGetFileContentsImpl GetFileContents input = getFileContentsImpl input getFileContentsImpl - :: HasInput i AllHaskellFiles - => InputPath i + :: InputPath AllHaskellFiles -> Action ([FileDiagnostic], Maybe (FileVersion, Maybe T.Text)) getFileContentsImpl file = do -- need to depend on modification time to introduce a dependency with Cutoff @@ -197,7 +195,7 @@ getFileContentsImpl file = do -- | Returns the modification time and the contents. -- For VFS paths, the modification time is the current time. -getFileContents :: HasInput i AllHaskellFiles => InputPath i -> Action (UTCTime, Maybe T.Text) +getFileContents :: InputPath AllHaskellFiles -> Action (UTCTime, Maybe T.Text) getFileContents f = do (fv, txt) <- use_ GetFileContents f modTime <- case modificationTime fv of @@ -211,10 +209,10 @@ getFileContents f = do pure $ posixSecondsToUTCTime posix return (modTime, txt) -fileStoreRules :: forall i. HasInput i AllHaskellFiles => Recorder (WithPriority Log) -> (InputPath i -> Action Bool) -> Rules () +fileStoreRules :: Recorder (WithPriority Log) -> (InputPath AllHaskellFiles -> Action Bool) -> Rules () fileStoreRules recorder isWatched = do - getModificationTimeRule @i recorder - getFileContentsRule @i recorder + getModificationTimeRule recorder + getFileContentsRule recorder addWatchedFileRule recorder isWatched -- | Note that some buffer for a specific file has been modified but not @@ -250,9 +248,8 @@ typecheckParentsAction recorder nfp = do Nothing -> logWith recorder Info $ LogCouldNotIdentifyReverseDeps nfp Just rs -> do logWith recorder Info $ LogTypeCheckingReverseDeps nfp revs - let partitionedInputs = partitionInputs rs - void $ uses GetModIface (projectFiles partitionedInputs) - void $ uses GetModIface (dependencyFiles partitionedInputs) + let classifiedInputs = classifyAllHaskellInputs rs + void $ uses GetModIface classifiedInputs -- | Note that some keys have been modified and restart the session -- Only valid if the virtual file system was initialised by LSP, as that diff --git a/ghcide/src/Development/IDE/Core/InputPath.hs b/ghcide/src/Development/IDE/Core/InputPath.hs index 9bb7cdc861..d3e2a41dbe 100644 --- a/ghcide/src/Development/IDE/Core/InputPath.hs +++ b/ghcide/src/Development/IDE/Core/InputPath.hs @@ -6,34 +6,18 @@ import Control.DeepSeq import Data.Hashable import Data.List (isInfixOf) import Data.Typeable -import Development.IDE.Types.Location (emptyFilePath) +import Development.IDE.Graph.Internal.Rules (InputClass(..)) import Language.LSP.Protocol.Types (NormalizedFilePath, fromNormalizedFilePath) import System.FilePath (splitDirectories) -data InputClass - = ProjectHaskellFiles - | AllHaskellFiles - newtype InputPath (i :: InputClass) = InputPath { unInputPath :: NormalizedFilePath } deriving newtype (Eq, Hashable, NFData, Typeable, Show) -class HasNormalizedFilePath input where - getNormalizedFilePath :: input -> NormalizedFilePath - -instance HasNormalizedFilePath (InputPath ProjectHaskellFiles) where - getNormalizedFilePath (InputPath nfp) = nfp - -instance HasNormalizedFilePath (InputPath AllHaskellFiles) where - getNormalizedFilePath (InputPath nfp) = nfp - -instance HasNormalizedFilePath () where - getNormalizedFilePath _ = emptyFilePath - -- All Haskell files are valid, and we assume all -- files are Haskell files (for now) so there is -- no need to filter out any FilePaths. -classifyAllHaskellInputs :: [NormalizedFilePath] -> [InputPath ProjectHaskellFiles] +classifyAllHaskellInputs :: [NormalizedFilePath] -> [InputPath AllHaskellFiles] classifyAllHaskellInputs = map InputPath -- Dependency files should not be considered diff --git a/ghcide/src/Development/IDE/Core/OfInterest.hs b/ghcide/src/Development/IDE/Core/OfInterest.hs index 2a594c1021..81fd6ca272 100644 --- a/ghcide/src/Development/IDE/Core/OfInterest.hs +++ b/ghcide/src/Development/IDE/Core/OfInterest.hs @@ -50,6 +50,7 @@ import Ide.Logger (Pretty (pretty), logWith) import qualified Language.LSP.Protocol.Message as LSP import qualified Language.LSP.Server as LSP +import Development.IDE.Core.InputPath (classifyProjectHaskellInputs, classifyAllHaskellInputs) data Log = LogShake Shake.Log deriving Show @@ -134,6 +135,8 @@ scheduleGarbageCollection state = do kick :: Action () kick = do files <- HashMap.keys <$> getFilesOfInterestUntracked + let classifiedHaskellFiles = classifyAllHaskellInputs files + classifiedProjectFiles = classifyProjectHaskellInputs files ShakeExtras{exportsMap, ideTesting = IdeTesting testing, lspEnv, progress} <- getShakeExtras let signal :: KnownSymbol s => Proxy s -> Action () signal msg = when testing $ liftIO $ @@ -145,11 +148,11 @@ kick = do liftIO $ progressUpdate progress ProgressNewStarted -- Update the exports map - results <- uses GenerateCore files - <* uses GetHieAst files + results <- uses GenerateCore classifiedProjectFiles + <* uses GetHieAst classifiedHaskellFiles -- needed to have non local completions on the first edit -- when the first edit breaks the module header - <* uses NonLocalCompletions files + <* uses NonLocalCompletions classifiedProjectFiles let mguts = catMaybes results void $ liftIO $ atomically $ modifyTVar' exportsMap (updateExportsMapMg mguts) diff --git a/ghcide/src/Development/IDE/Core/RuleTypes.hs b/ghcide/src/Development/IDE/Core/RuleTypes.hs index 8d9f9d6e4f..d80e04d3c9 100644 --- a/ghcide/src/Development/IDE/Core/RuleTypes.hs +++ b/ghcide/src/Development/IDE/Core/RuleTypes.hs @@ -23,13 +23,13 @@ import Data.Hashable import qualified Data.Map as M import Data.Time.Clock.POSIX import Data.Typeable -import Development.IDE.Core.InputPath import Development.IDE.GHC.Compat hiding (HieFileResult) import Development.IDE.GHC.Compat.Util import Development.IDE.GHC.CoreFile import Development.IDE.GHC.Util import Development.IDE.Graph +import Development.IDE.Graph.Internal.Rules (InputClass(..)) import Development.IDE.Import.DependencyInformation import Development.IDE.Types.HscEnvEq (HscEnvEq) import Development.IDE.Types.KnownTargets @@ -66,26 +66,26 @@ encodeLinkableType (Just ObjectLinkable) = "2" -- | The parse tree for the file using GetFileContents type instance RuleResult GetParsedModule = ParsedModule -type instance RuleInput GetParsedModule = InputPath AllHaskellFiles +type instance RuleInput GetParsedModule = AllHaskellFiles -- | The parse tree for the file using GetFileContents, -- all comments included using Opt_KeepRawTokenStream type instance RuleResult GetParsedModuleWithComments = ParsedModule -type instance RuleInput GetParsedModuleWithComments = InputPath AllHaskellFiles +type instance RuleInput GetParsedModuleWithComments = AllHaskellFiles type instance RuleResult GetModuleGraph = DependencyInformation -type instance RuleInput GetModuleGraph = () +type instance RuleInput GetModuleGraph = NoFile data GetKnownTargets = GetKnownTargets deriving (Show, Generic, Eq, Ord) instance Hashable GetKnownTargets instance NFData GetKnownTargets type instance RuleResult GetKnownTargets = KnownTargets -type instance RuleInput GetKnownTargets = () +type instance RuleInput GetKnownTargets = NoFile -- | Convert to Core, requires TypeCheck* type instance RuleResult GenerateCore = ModGuts -type instance RuleInput GenerateCore = InputPath ProjectHaskellFiles +type instance RuleInput GenerateCore = ProjectHaskellFiles data GenerateCore = GenerateCore deriving (Eq, Show, Typeable, Generic) @@ -93,7 +93,7 @@ instance Hashable GenerateCore instance NFData GenerateCore type instance RuleResult GetLinkable = LinkableResult -type instance RuleInput GetLinkable = InputPath ProjectHaskellFiles +type instance RuleInput GetLinkable = ProjectHaskellFiles data LinkableResult = LinkableResult @@ -119,7 +119,7 @@ instance Hashable GetImportMap instance NFData GetImportMap type instance RuleResult GetImportMap = ImportMap -type instance RuleInput GetImportMap = InputPath ProjectHaskellFiles +type instance RuleInput GetImportMap = ProjectHaskellFiles newtype ImportMap = ImportMap { importMap :: M.Map ModuleName NormalizedFilePath -- ^ Where are the modules imported by this file located? } deriving stock Show @@ -240,15 +240,15 @@ instance Show HieAstResult where -- | The type checked version of this file, requires TypeCheck+ type instance RuleResult TypeCheck = TcModuleResult -type instance RuleInput TypeCheck = InputPath ProjectHaskellFiles +type instance RuleInput TypeCheck = ProjectHaskellFiles -- | The uncompressed HieAST type instance RuleResult GetHieAst = HieAstResult -type instance RuleInput GetHieAst = InputPath AllHaskellFiles +type instance RuleInput GetHieAst = AllHaskellFiles -- | A IntervalMap telling us what is in scope at each point type instance RuleResult GetBindings = Bindings -type instance RuleInput GetBindings = InputPath ProjectHaskellFiles +type instance RuleInput GetBindings = ProjectHaskellFiles data DocAndTyThingMap = DKMap {getDocMap :: !DocMap, getTyThingMap :: !TyThingMap} instance NFData DocAndTyThingMap where @@ -258,50 +258,50 @@ instance Show DocAndTyThingMap where show = const "docmap" type instance RuleResult GetDocMap = DocAndTyThingMap -type instance RuleInput GetDocMap = InputPath ProjectHaskellFiles +type instance RuleInput GetDocMap = ProjectHaskellFiles -- | A GHC session that we reuse. type instance RuleResult GhcSession = HscEnvEq -type instance RuleInput GhcSession = InputPath ProjectHaskellFiles +type instance RuleInput GhcSession = ProjectHaskellFiles -- | A GHC session preloaded with all the dependencies -- This rule is also responsible for calling ReportImportCycles for the direct dependencies type instance RuleResult GhcSessionDeps = HscEnvEq -type instance RuleInput GhcSessionDeps = InputPath ProjectHaskellFiles +type instance RuleInput GhcSessionDeps = ProjectHaskellFiles -- | Resolve the imports in a module to the file path of a module in the same package type instance RuleResult GetLocatedImports = [(Located ModuleName, Maybe ArtifactsLocation)] -type instance RuleInput GetLocatedImports = InputPath ProjectHaskellFiles +type instance RuleInput GetLocatedImports = ProjectHaskellFiles -- | This rule is used to report import cycles. It depends on GetModuleGraph. -- We cannot report the cycles directly from GetModuleGraph since -- we can only report diagnostics for the current file. type instance RuleResult ReportImportCycles = () -type instance RuleInput ReportImportCycles = InputPath ProjectHaskellFiles +type instance RuleInput ReportImportCycles = ProjectHaskellFiles -- | Read the module interface file from disk. Throws an error for VFS files. -- This is an internal rule, use 'GetModIface' instead. type instance RuleResult GetModIfaceFromDisk = HiFileResult -type instance RuleInput GetModIfaceFromDisk = InputPath ProjectHaskellFiles +type instance RuleInput GetModIfaceFromDisk = ProjectHaskellFiles -- | GetModIfaceFromDisk and index the `.hie` file into the database. -- This is an internal rule, use 'GetModIface' instead. type instance RuleResult GetModIfaceFromDiskAndIndex = HiFileResult -type instance RuleInput GetModIfaceFromDiskAndIndex = InputPath ProjectHaskellFiles +type instance RuleInput GetModIfaceFromDiskAndIndex = ProjectHaskellFiles -- | Get a module interface details, either from an interface file or a typechecked module type instance RuleResult GetModIface = HiFileResult -type instance RuleInput GetModIface = InputPath AllHaskellFiles +type instance RuleInput GetModIface = AllHaskellFiles -- | Get the contents of a file, either dirty (if the buffer is modified) or Nothing to mean use from disk. type instance RuleResult GetFileContents = (FileVersion, Maybe Text) -type instance RuleInput GetFileContents = InputPath AllHaskellFiles +type instance RuleInput GetFileContents = AllHaskellFiles type instance RuleResult GetFileExists = Bool -type instance RuleInput GetFileExists = InputPath AllHaskellFiles +type instance RuleInput GetFileExists = AllHaskellFiles type instance RuleResult AddWatchedFile = Bool -type instance RuleInput AddWatchedFile = InputPath AllHaskellFiles +type instance RuleInput AddWatchedFile = AllHaskellFiles -- The Shake key type for getModificationTime queries @@ -331,7 +331,7 @@ pattern GetModificationTime = GetModificationTime_ {missingFileDiagnostics=True} -- | Get the modification time of a file. type instance RuleResult GetModificationTime = FileVersion -type instance RuleInput GetModificationTime = InputPath AllHaskellFiles +type instance RuleInput GetModificationTime = AllHaskellFiles -- | Either the mtime from disk or an LSP version -- LSP versions always compare as greater than on disk versions @@ -374,7 +374,7 @@ instance Hashable IsFileOfInterestResult instance NFData IsFileOfInterestResult type instance RuleResult IsFileOfInterest = IsFileOfInterestResult -type instance RuleInput IsFileOfInterest = InputPath AllHaskellFiles +type instance RuleInput IsFileOfInterest = AllHaskellFiles data ModSummaryResult = ModSummaryResult { msrModSummary :: !ModSummary @@ -397,11 +397,11 @@ instance NFData ModSummaryResult where -- | Generate a ModSummary that has enough information to be used to get .hi and .hie files. -- without needing to parse the entire source type instance RuleResult GetModSummary = ModSummaryResult -type instance RuleInput GetModSummary = InputPath AllHaskellFiles +type instance RuleInput GetModSummary = AllHaskellFiles -- | Generate a ModSummary with the timestamps and preprocessed content elided, for more successful early cutoff type instance RuleResult GetModSummaryWithoutTimestamps = ModSummaryResult -type instance RuleInput GetModSummaryWithoutTimestamps = InputPath AllHaskellFiles +type instance RuleInput GetModSummaryWithoutTimestamps = AllHaskellFiles data GetParsedModule = GetParsedModule deriving (Eq, Show, Typeable, Generic) @@ -420,7 +420,7 @@ instance NFData GetLocatedImports -- | Does this module need to be compiled? type instance RuleResult NeedsCompilation = Maybe LinkableType -type instance RuleInput NeedsCompilation = InputPath ProjectHaskellFiles +type instance RuleInput NeedsCompilation = ProjectHaskellFiles data NeedsCompilation = NeedsCompilation deriving (Eq, Show, Typeable, Generic) @@ -514,7 +514,7 @@ instance Hashable GetClientSettings instance NFData GetClientSettings type instance RuleResult GetClientSettings = Hashed (Maybe Value) -type instance RuleInput GetClientSettings = () +type instance RuleInput GetClientSettings = NoFile data AddWatchedFile = AddWatchedFile deriving (Eq, Show, Typeable, Generic) instance Hashable AddWatchedFile @@ -525,7 +525,7 @@ instance NFData AddWatchedFile -- thread killed exception issues, so we lift it to a full rule. -- https://github.com/digital-asset/daml/pull/2808#issuecomment-529639547 type instance RuleResult GhcSessionIO = IdeGhcSession -type instance RuleInput GhcSessionIO = InputPath ProjectHaskellFiles +type instance RuleInput GhcSessionIO = ProjectHaskellFiles data IdeGhcSession = IdeGhcSession { loadSessionFun :: FilePath -> IO (IdeResult HscEnvEq, [FilePath]) diff --git a/ghcide/src/Development/IDE/Core/Shake.hs b/ghcide/src/Development/IDE/Core/Shake.hs index bc1eec1cc5..eaf3bd365b 100644 --- a/ghcide/src/Development/IDE/Core/Shake.hs +++ b/ghcide/src/Development/IDE/Core/Shake.hs @@ -146,6 +146,7 @@ import Development.IDE.Graph.Database (ShakeDatabase, shakeNewDatabase, shakeProfileDatabase, shakeRunDatabaseForKeys) +import Development.IDE.Graph.Internal.Rules (InputClass(..)) import Development.IDE.Graph.Rule import Development.IDE.Types.Action import Development.IDE.Types.Diagnostics @@ -181,7 +182,8 @@ import System.FilePath hiding (makeRelative) import System.IO.Unsafe (unsafePerformIO) import System.Time.Extra import UnliftIO (MonadUnliftIO (withRunInIO)) -import Development.IDE.Core.InputPath (HasNormalizedFilePath(getNormalizedFilePath)) +import Development.IDE.Core.InputPath (InputPath(..)) +import Development.IDE.Types.Location (emptyFilePath) data Log = LogCreateHieDbExportsMapStart @@ -454,10 +456,11 @@ getIdeOptionsIO ide = do -- | Return the most recent, potentially stale, value and a PositionMapping -- for the version of that value. -lastValueIO :: IdeRule k i v => ShakeExtras -> k -> i -> IO (Maybe (v, PositionMapping)) +lastValueIO :: IdeRule k i v => ShakeExtras -> k -> InputPath i -> IO (Maybe (v, PositionMapping)) lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k input = do - let readPersistent + let rawFile = unInputPath input + readPersistent | IdeTesting testing <- ideTesting s -- Don't read stale persistent values in tests , testing = pure Nothing | otherwise = do @@ -465,20 +468,20 @@ lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k input = do mv <- runMaybeT $ do liftIO $ logWith (shakeRecorder s) Debug $ LogLookupPersistentKey (T.pack $ show k) f <- MaybeT $ pure $ lookupKeyMap (newKey k) pmap - (dv,del,ver) <- MaybeT $ runIdeAction "lastValueIO" s $ f $ getNormalizedFilePath input + (dv,del,ver) <- MaybeT $ runIdeAction "lastValueIO" s $ f rawFile MaybeT $ pure $ (,del,ver) <$> fromDynamic dv case mv of Nothing -> atomicallyNamed "lastValueIO 1" $ do - STM.focus (Focus.alter (alterValue $ Failed True)) (toKey k $ getNormalizedFilePath input) state + STM.focus (Focus.alter (alterValue $ Failed True)) (toKey k rawFile) state return Nothing Just (v,del,mbVer) -> do actual_version <- case mbVer of Just ver -> pure (Just $ VFSVersion ver) - Nothing -> (Just . ModificationTime <$> getModTime (fromNormalizedFilePath $ getNormalizedFilePath input)) + Nothing -> (Just . ModificationTime <$> getModTime (fromNormalizedFilePath rawFile)) `catch` (\(_ :: IOException) -> pure Nothing) atomicallyNamed "lastValueIO 2" $ do - STM.focus (Focus.alter (alterValue $ Stale (Just del) actual_version (toDyn v))) (toKey k input) state - Just . (v,) . addOldDelta del <$> mappingForVersion positionMapping (getNormalizedFilePath input) actual_version + STM.focus (Focus.alter (alterValue $ Stale (Just del) actual_version (toDyn v))) (toKey k rawFile) state + Just . (v,) . addOldDelta del <$> mappingForVersion positionMapping rawFile actual_version -- We got a new stale value from the persistent rule, insert it in the map without affecting diagnostics alterValue new Nothing = Just (ValueWithDiagnostics new mempty) -- If it wasn't in the map, give it empty diagnostics @@ -488,19 +491,19 @@ lastValueIO s@ShakeExtras{positionMapping,persistentKeys,state} k input = do -- Something already succeeded before, leave it alone _ -> old - atomicallyNamed "lastValueIO 4" (STM.lookup (toKey k input) state) >>= \case + atomicallyNamed "lastValueIO 4" (STM.lookup (toKey k rawFile) state) >>= \case Nothing -> readPersistent Just (ValueWithDiagnostics value _) -> case value of Succeeded ver (fromDynamic -> Just v) -> - atomicallyNamed "lastValueIO 5" $ Just . (v,) <$> mappingForVersion positionMapping (getNormalizedFilePath input) ver + atomicallyNamed "lastValueIO 5" $ Just . (v,) <$> mappingForVersion positionMapping rawFile ver Stale del ver (fromDynamic -> Just v) -> - atomicallyNamed "lastValueIO 6" $ Just . (v,) . maybe id addOldDelta del <$> mappingForVersion positionMapping (getNormalizedFilePath input) ver + atomicallyNamed "lastValueIO 6" $ Just . (v,) . maybe id addOldDelta del <$> mappingForVersion positionMapping rawFile ver Failed p | not p -> readPersistent _ -> pure Nothing -- | Return the most recent, potentially stale, value and a PositionMapping -- for the version of that value. -lastValue :: IdeRule k i v => k -> i -> Action (Maybe (v, PositionMapping)) +lastValue :: IdeRule k i v => k -> InputPath i -> Action (Maybe (v, PositionMapping)) lastValue key file = do s <- getShakeExtras liftIO $ lastValueIO s key file @@ -519,8 +522,6 @@ type IdeRule k i v = ( Shake.RuleInput k ~ i , Shake.RuleResult k ~ v , Shake.ShakeValue k - , Shake.ShakeValue i - , HasNormalizedFilePath i , Show v , Typeable v , NFData v @@ -589,12 +590,12 @@ shakeDatabaseProfileIO mbProfileDir = do setValues :: IdeRule k i v => Values -> k - -> i + -> InputPath i -> Value v -> Vector FileDiagnostic -> STM () setValues state key input val diags = - STM.insert (ValueWithDiagnostics (fmap toDyn val) diags) (toKey key $ getNormalizedFilePath input) state + STM.insert (ValueWithDiagnostics (fmap toDyn val) diags) (toKey key $ unInputPath input) state -- | Delete the value stored for a given ide build key @@ -616,10 +617,10 @@ getValues :: IdeRule k i v => Values -> k -> - i -> + InputPath i -> STM (Maybe (Value v, Vector FileDiagnostic)) getValues state key input = do - STM.lookup (toKey key $ getNormalizedFilePath input) state >>= \case + STM.lookup (toKey key $ unInputPath input) state >>= \case Nothing -> pure Nothing Just (ValueWithDiagnostics v diagsV) -> do let !r = seqValue $ fmap (fromJust . fromDynamic @v) v @@ -1016,22 +1017,22 @@ preservedKeys checkParents = HSet.fromList $ -- | Define a new Rule without early cutoff define :: IdeRule k i v - => Recorder (WithPriority Log) -> (k -> i -> Action (IdeResult v)) -> Rules () + => Recorder (WithPriority Log) -> (k -> InputPath i -> Action (IdeResult v)) -> Rules () define recorder op = defineEarlyCutoff recorder $ Rule $ \k v -> (Nothing,) <$> op k v defineNoDiagnostics :: IdeRule k i v - => Recorder (WithPriority Log) -> (k -> i -> Action (Maybe v)) -> Rules () + => Recorder (WithPriority Log) -> (k -> InputPath i -> Action (Maybe v)) -> Rules () defineNoDiagnostics recorder op = defineEarlyCutoff recorder $ RuleNoDiagnostics $ \k v -> (Nothing,) <$> op k v -- | Request a Rule result if available use :: IdeRule k i v - => k -> i -> Action (Maybe v) + => k -> InputPath i -> Action (Maybe v) use key input = runIdentity <$> uses key (Identity input) -- | Request a Rule result, it not available return the last computed result, if any, which may be stale useWithStale :: IdeRule k i v - => k -> i -> Action (Maybe (v, PositionMapping)) + => k -> InputPath i -> Action (Maybe (v, PositionMapping)) useWithStale key file = runIdentity <$> usesWithStale key (Identity file) -- |Request a Rule result, it not available return the last computed result @@ -1042,7 +1043,7 @@ useWithStale key file = runIdentity <$> usesWithStale key (Identity file) -- -- WARNING: Not suitable for PluginHandlers. Use `useWithStaleE` instead. useWithStale_ :: IdeRule k i v - => k -> i -> Action (v, PositionMapping) + => k -> InputPath i -> Action (v, PositionMapping) useWithStale_ key input = runIdentity <$> usesWithStale_ key (Identity input) -- |Plural version of 'useWithStale_' @@ -1051,7 +1052,7 @@ useWithStale_ key input = runIdentity <$> usesWithStale_ key (Identity input) -- none available. -- -- WARNING: Not suitable for PluginHandlers. -usesWithStale_ :: (Traversable f, IdeRule k i v) => k -> f i -> Action (f (v, PositionMapping)) +usesWithStale_ :: (Traversable f, IdeRule k i v) => k -> f (InputPath i) -> Action (f (v, PositionMapping)) usesWithStale_ key inputs = do res <- usesWithStale key inputs case sequence res of @@ -1082,11 +1083,11 @@ data FastResult a = FastResult { stale :: Maybe (a,PositionMapping), uptoDate :: -- | Lookup value in the database and return with the stale value immediately -- Will queue an action to refresh the value. -- Might block the first time the rule runs, but never blocks after that. -useWithStaleFast :: IdeRule k i v => k -> i -> IdeAction (Maybe (v, PositionMapping)) +useWithStaleFast :: IdeRule k i v => k -> InputPath i -> IdeAction (Maybe (v, PositionMapping)) useWithStaleFast key input = stale <$> useWithStaleFast' key input -- | Same as useWithStaleFast but lets you wait for an up to date result -useWithStaleFast' :: IdeRule k i v => k -> i -> IdeAction (FastResult v) +useWithStaleFast' :: IdeRule k i v => k -> InputPath i -> IdeAction (FastResult v) useWithStaleFast' key input = do -- This lookup directly looks up the key in the shake database and -- returns the last value that was computed for this key without @@ -1094,7 +1095,7 @@ useWithStaleFast' key input = do -- Async trigger the key to be built anyway because we want to -- keep updating the value in the key. - waitValue <- delayedAction $ mkDelayedAction ("C:" ++ show key ++ ":" ++ fromNormalizedFilePath (getNormalizedFilePath input)) Debug $ use key input + waitValue <- delayedAction $ mkDelayedAction ("C:" ++ show key ++ ":" ++ fromNormalizedFilePath (unInputPath input)) Debug $ use key input s@ShakeExtras{state} <- askShake r <- liftIO $ atomicallyNamed "useStateFast" $ getValues state key input @@ -1113,8 +1114,8 @@ useWithStaleFast' key input = do res <- lastValueIO s key input pure $ FastResult res waitValue -useNoFile :: forall k is v. IdeRule k () v => k -> Action (Maybe v) -useNoFile key = use key () +useNoFile :: forall k v. IdeRule k NoFile v => k -> Action (Maybe v) +useNoFile key = use key (InputPath emptyFilePath) -- Requests a rule if available. -- @@ -1122,11 +1123,11 @@ useNoFile key = use key () -- none available. -- -- WARNING: Not suitable for PluginHandlers. Use `useE` instead. -use_ :: IdeRule k i v => k -> i -> Action v +use_ :: IdeRule k i v => k -> InputPath i -> Action v use_ key input = runIdentity <$> uses_ key (Identity input) -useNoFile_ :: forall k is v. IdeRule k () v => k -> Action v -useNoFile_ key = use_ key () +useNoFile_ :: forall k v. IdeRule k NoFile v => k -> Action v +useNoFile_ key = use_ key (InputPath emptyFilePath) -- |Plural version of `use_` -- @@ -1134,7 +1135,7 @@ useNoFile_ key = use_ key () -- none available. -- -- WARNING: Not suitable for PluginHandlers. Use `usesE` instead. -uses_ :: (Traversable f, IdeRule k i v) => k -> f i -> Action (f v) +uses_ :: (Traversable f, IdeRule k i v) => k -> f (InputPath i) -> Action (f v) uses_ key files = do res <- uses key files case sequence res of @@ -1143,32 +1144,32 @@ uses_ key files = do -- | Plural version of 'use' uses :: (Traversable f, IdeRule k i v) - => k -> f i -> Action (f (Maybe v)) -uses key files = fmap (\(A value) -> currentValue value) <$> apply (fmap (Q . (key,) . getNormalizedFilePath) files) + => k -> f (InputPath i) -> Action (f (Maybe v)) +uses key files = fmap (\(A value) -> currentValue value) <$> apply (fmap (Q . (key,) . unInputPath) files) -- | Return the last computed result which might be stale. usesWithStale :: (Traversable f, IdeRule k i v) - => k -> f i -> Action (f (Maybe (v, PositionMapping))) + => k -> f (InputPath i) -> Action (f (Maybe (v, PositionMapping))) usesWithStale key inputs = do - _ <- apply (fmap (Q . (key,) . getNormalizedFilePath) inputs) + _ <- apply (fmap (Q . (key,) . unInputPath) inputs) -- We don't look at the result of the 'apply' since 'lastValue' will -- return the most recent successfully computed value regardless of -- whether the rule succeeded or not. traverse (lastValue key) inputs useWithoutDependency :: IdeRule k i v - => k -> i -> Action (Maybe v) + => k -> InputPath i -> Action (Maybe v) useWithoutDependency key input = - (\(Identity (A value)) -> currentValue value) <$> applyWithoutDependency (Identity (Q (key, getNormalizedFilePath input))) + (\(Identity (A value)) -> currentValue value) <$> applyWithoutDependency (Identity (Q (key, unInputPath input))) data RuleBody k i v - = Rule (k -> i -> Action (Maybe BS.ByteString, IdeResult v)) - | RuleNoDiagnostics (k -> i -> Action (Maybe BS.ByteString, Maybe v)) + = Rule (k -> InputPath i -> Action (Maybe BS.ByteString, IdeResult v)) + | RuleNoDiagnostics (k -> InputPath i -> Action (Maybe BS.ByteString, Maybe v)) | RuleWithCustomNewnessCheck { newnessCheck :: BS.ByteString -> BS.ByteString -> Bool - , build :: k -> i -> Action (Maybe BS.ByteString, Maybe v) + , build :: k -> InputPath i -> Action (Maybe BS.ByteString, Maybe v) } - | RuleWithOldValue (k -> i -> Value v -> Action (Maybe BS.ByteString, IdeResult v)) + | RuleWithOldValue (k -> InputPath i -> Value v -> Action (Maybe BS.ByteString, IdeResult v)) -- | Define a new Rule with early cutoff defineEarlyCutoff @@ -1177,38 +1178,42 @@ defineEarlyCutoff => Recorder (WithPriority Log) -> RuleBody k i v -> Rules () -defineEarlyCutoff recorder (Rule op) = addRule $ \(Q (key, input)) (old :: Maybe BS.ByteString) mode -> otTracedAction key (getNormalizedFilePath input) mode traceA $ \traceDiagnostics -> do +defineEarlyCutoff recorder (Rule op) = addRule $ \(Q (key, rawFile)) (old :: Maybe BS.ByteString) mode -> otTracedAction key rawFile mode traceA $ \traceDiagnostics -> do extras <- getShakeExtras let diagnostics ver diags = do traceDiagnostics diags - updateFileDiagnostics recorder (getNormalizedFilePath input) ver (newKey key) extras . map (\(_,y,z) -> (y,z)) $ diags + updateFileDiagnostics recorder rawFile ver (newKey key) extras . map (\(_,y,z) -> (y,z)) $ diags + input = InputPath @i rawFile defineEarlyCutoff' diagnostics (==) key input old mode $ const $ op key input -defineEarlyCutoff recorder (RuleNoDiagnostics op) = addRule $ \(Q (key, input)) (old :: Maybe BS.ByteString) mode -> otTracedAction key (getNormalizedFilePath input) mode traceA $ \traceDiagnostics -> do +defineEarlyCutoff recorder (RuleNoDiagnostics op) = addRule $ \(Q (key, rawFile)) (old :: Maybe BS.ByteString) mode -> otTracedAction key rawFile mode traceA $ \traceDiagnostics -> do let diagnostics _ver diags = do traceDiagnostics diags mapM_ (logWith recorder Warning . LogDefineEarlyCutoffRuleNoDiagHasDiag) diags + input = InputPath @i rawFile defineEarlyCutoff' diagnostics (==) key input old mode $ const $ second (mempty,) <$> op key input defineEarlyCutoff recorder RuleWithCustomNewnessCheck{..} = - addRule $ \(Q (key, input)) (old :: Maybe BS.ByteString) mode -> - otTracedAction key (getNormalizedFilePath input) mode traceA $ \ traceDiagnostics -> do + addRule $ \(Q (key, rawFile)) (old :: Maybe BS.ByteString) mode -> + otTracedAction key rawFile mode traceA $ \ traceDiagnostics -> do let diagnostics _ver diags = do traceDiagnostics diags mapM_ (logWith recorder Warning . LogDefineEarlyCutoffRuleCustomNewnessHasDiag) diags + input = InputPath @i rawFile defineEarlyCutoff' diagnostics newnessCheck key input old mode $ const $ second (mempty,) <$> build key input -defineEarlyCutoff recorder (RuleWithOldValue op) = addRule $ \(Q (key, input)) (old :: Maybe BS.ByteString) mode -> otTracedAction key (getNormalizedFilePath input) mode traceA $ \traceDiagnostics -> do +defineEarlyCutoff recorder (RuleWithOldValue op) = addRule $ \(Q (key, rawFile)) (old :: Maybe BS.ByteString) mode -> otTracedAction key rawFile mode traceA $ \traceDiagnostics -> do extras <- getShakeExtras let diagnostics ver diags = do traceDiagnostics diags - updateFileDiagnostics recorder (getNormalizedFilePath input) ver (newKey key) extras . map (\(_,y,z) -> (y,z)) $ diags + updateFileDiagnostics recorder rawFile ver (newKey key) extras . map (\(_,y,z) -> (y,z)) $ diags + input = InputPath @i rawFile defineEarlyCutoff' diagnostics (==) key input old mode $ op key input -defineNoFile :: forall k v. IdeRule k () v => Recorder (WithPriority Log) -> (k -> Action v) -> Rules () +defineNoFile :: forall k v. IdeRule k NoFile v => Recorder (WithPriority Log) -> (k -> Action v) -> Rules () defineNoFile recorder f = defineNoDiagnostics recorder $ \k _ -> do res <- f k return (Just res) -defineEarlyCutOffNoFile :: forall k v. IdeRule k () v => Recorder (WithPriority Log) -> (k -> Action (BS.ByteString, v)) -> Rules () +defineEarlyCutOffNoFile :: forall k v. IdeRule k NoFile v => Recorder (WithPriority Log) -> (k -> Action (BS.ByteString, v)) -> Rules () defineEarlyCutOffNoFile recorder f = defineEarlyCutoff recorder $ RuleNoDiagnostics $ \k _ -> do (hashString, res) <- f k return (Just hashString, Just res) @@ -1219,13 +1224,13 @@ defineEarlyCutoff' -- | compare current and previous for freshness -> (BS.ByteString -> BS.ByteString -> Bool) -> k - -> i + -> InputPath i -> Maybe BS.ByteString -> RunMode -> (Value v -> Action (Maybe BS.ByteString, IdeResult v)) -> Action (RunResult (A (RuleResult k))) defineEarlyCutoff' doDiagnostics cmp key input mbOld mode action = do - let rawFile = getNormalizedFilePath input + let rawFile = unInputPath input ShakeExtras{state, progress, dirtyKeys} <- getShakeExtras options <- getIdeOptions let trans g x = withRunInIO $ \run -> g (run x) @@ -1277,7 +1282,7 @@ defineEarlyCutoff' doDiagnostics cmp key input mbOld mode action = do -- this hook needs to be run in the same transaction as the key is marked clean -- see Note [Housekeeping rule cache and dirty key outside of hls-graph] setValues state key input res (Vector.fromList diags) - modifyTVar' dirtyKeys (deleteKeySet $ toKey key input) + modifyTVar' dirtyKeys (deleteKeySet $ toKey key rawFile) return res where -- Highly unsafe helper to compute the version of a file @@ -1286,10 +1291,10 @@ defineEarlyCutoff' doDiagnostics cmp key input mbOld mode action = do estimateFileVersionUnsafely :: k -> Maybe v - -> i + -> InputPath i -> Action (Maybe FileVersion) estimateFileVersionUnsafely _k v input - | Just Refl <- eqT @i @() = pure Nothing + | unInputPath input == emptyFilePath = pure Nothing | Just Refl <- eqT @k @GetModificationTime = pure v -- GetModificationTime depends on these rules, so avoid creating a cycle | Just Refl <- eqT @k @AddWatchedFile = pure Nothing @@ -1299,7 +1304,7 @@ defineEarlyCutoff' doDiagnostics cmp key input mbOld mode action = do -- For all other rules - compute the version properly without: -- * creating a dependency: If everything depends on GetModificationTime, we lose early cutoff -- * creating bogus "file does not exists" diagnostics - | otherwise = useWithoutDependency (GetModificationTime_ False) (InputPath $ getNormalizedFilePath input) + | otherwise = useWithoutDependency (GetModificationTime_ False) (InputPath $ unInputPath input) -- Note [Housekeeping rule cache and dirty key outside of hls-graph] -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1464,10 +1469,10 @@ kickSignal testing lspEnv files msg = when testing $ liftIO $ mRunLspT lspEnv $ toJSON $ map fromNormalizedFilePath files -- | Add kick start/done signal to rule -runWithSignal :: (KnownSymbol s0, KnownSymbol s1, IdeRule k i v) => Proxy s0 -> Proxy s1 -> [i] -> k -> Action () -runWithSignal msgStart msgEnd inputFiles rule = do - let files = map getNormalizedFilePath inputFiles +runWithSignal :: (KnownSymbol s0, KnownSymbol s1, IdeRule k i v) => Proxy s0 -> Proxy s1 -> [InputPath i] -> k -> Action () +runWithSignal msgStart msgEnd inputs rule = do + let files = map unInputPath inputs ShakeExtras{ideTesting = Options.IdeTesting testing, lspEnv} <- getShakeExtras kickSignal testing lspEnv files msgStart - void $ uses rule inputFiles + void $ uses rule inputs kickSignal testing lspEnv files msgEnd diff --git a/ghcide/src/Development/IDE/Plugin/Completions/Types.hs b/ghcide/src/Development/IDE/Plugin/Completions/Types.hs index 2d950d66a9..fda1c48d2b 100644 --- a/ghcide/src/Development/IDE/Plugin/Completions/Types.hs +++ b/ghcide/src/Development/IDE/Plugin/Completions/Types.hs @@ -19,6 +19,7 @@ import Data.Text (Text) import Data.Typeable (Typeable) import Development.IDE.GHC.Compat import Development.IDE.Graph (RuleResult) +import Development.IDE.Graph.Internal.Rules (RuleInput, InputClass(..)) import Development.IDE.Spans.Common () import GHC.Generics (Generic) import qualified GHC.Types.Name.Occurrence as Occ @@ -28,7 +29,9 @@ import qualified Language.LSP.Protocol.Types as J -- | Produce completions info for a file type instance RuleResult LocalCompletions = CachedCompletions +type instance RuleInput LocalCompletions = ProjectHaskellFiles type instance RuleResult NonLocalCompletions = CachedCompletions +type instance RuleInput NonLocalCompletions = ProjectHaskellFiles data LocalCompletions = LocalCompletions deriving (Eq, Show, Typeable, Generic) diff --git a/hls-graph/src/Development/IDE/Graph/Internal/Rules.hs b/hls-graph/src/Development/IDE/Graph/Internal/Rules.hs index 00ff8fe641..561f11098c 100644 --- a/hls-graph/src/Development/IDE/Graph/Internal/Rules.hs +++ b/hls-graph/src/Development/IDE/Graph/Internal/Rules.hs @@ -22,7 +22,16 @@ import Development.IDE.Graph.Internal.Types -- | The type mapping between the @key@ or a rule and the resulting @value@. type family RuleResult key -- = value -type family RuleInput k -- input - (InputPath inputclass) or () + +-- | The broadest class of files a Rule is applicable to +data InputClass + = ProjectHaskellFiles + | AllHaskellFiles + | NoFile + +-- | The type mapping between the @key@ or a rule and the +-- class of files it is applicable to. +type family RuleInput key :: InputClass action :: Action a -> Rules () action x = do From dea842cd04cb613c21e0ff6a61a50323045e5bbe Mon Sep 17 00:00:00 2001 From: Elodie Lander Date: Sun, 17 Nov 2024 19:22:04 -0500 Subject: [PATCH 11/14] WIP fix Rules --- ghcide/src/Development/IDE/Core/FileStore.hs | 4 +- ghcide/src/Development/IDE/Core/InputPath.hs | 3 + ghcide/src/Development/IDE/Core/OfInterest.hs | 7 +- ghcide/src/Development/IDE/Core/RuleTypes.hs | 8 +- ghcide/src/Development/IDE/Core/Rules.hs | 129 +++++++++--------- .../IDE/Import/DependencyInformation.hs | 10 +- 6 files changed, 80 insertions(+), 81 deletions(-) diff --git a/ghcide/src/Development/IDE/Core/FileStore.hs b/ghcide/src/Development/IDE/Core/FileStore.hs index 507d24ec32..95d4fd72e3 100644 --- a/ghcide/src/Development/IDE/Core/FileStore.hs +++ b/ghcide/src/Development/IDE/Core/FileStore.hs @@ -70,7 +70,7 @@ import Language.LSP.VFS import System.FilePath import System.IO.Error import System.IO.Unsafe -import Development.IDE.Core.InputPath (InputPath (unInputPath), classifyAllHaskellInputs) +import Development.IDE.Core.InputPath (InputPath (unInputPath), classifyProjectHaskellInputs) import Development.IDE.Graph.Internal.Rules (InputClass(AllHaskellFiles)) @@ -248,7 +248,7 @@ typecheckParentsAction recorder nfp = do Nothing -> logWith recorder Info $ LogCouldNotIdentifyReverseDeps nfp Just rs -> do logWith recorder Info $ LogTypeCheckingReverseDeps nfp revs - let classifiedInputs = classifyAllHaskellInputs rs + let classifiedInputs = classifyProjectHaskellInputs rs void $ uses GetModIface classifiedInputs -- | Note that some keys have been modified and restart the session diff --git a/ghcide/src/Development/IDE/Core/InputPath.hs b/ghcide/src/Development/IDE/Core/InputPath.hs index d3e2a41dbe..602c0c65d4 100644 --- a/ghcide/src/Development/IDE/Core/InputPath.hs +++ b/ghcide/src/Development/IDE/Core/InputPath.hs @@ -41,3 +41,6 @@ classifyProjectHaskellInputs = foldr classifyInputPath [] dependencyDirectory = [".hls", "dependencies"] rawInput :: [FilePath] rawInput = splitDirectories (fromNormalizedFilePath nfp) + +generalizeProjectInput :: InputPath ProjectHaskellFiles -> InputPath AllHaskellFiles +generalizeProjectInput = InputPath . unInputPath diff --git a/ghcide/src/Development/IDE/Core/OfInterest.hs b/ghcide/src/Development/IDE/Core/OfInterest.hs index 81fd6ca272..8151572350 100644 --- a/ghcide/src/Development/IDE/Core/OfInterest.hs +++ b/ghcide/src/Development/IDE/Core/OfInterest.hs @@ -50,7 +50,7 @@ import Ide.Logger (Pretty (pretty), logWith) import qualified Language.LSP.Protocol.Message as LSP import qualified Language.LSP.Server as LSP -import Development.IDE.Core.InputPath (classifyProjectHaskellInputs, classifyAllHaskellInputs) +import Development.IDE.Core.InputPath (classifyProjectHaskellInputs, classifyAllHaskellInputs, InputPath (unInputPath)) data Log = LogShake Shake.Log deriving Show @@ -68,10 +68,11 @@ ofInterestRules :: Recorder (WithPriority Log) -> Rules () ofInterestRules recorder = do addIdeGlobal . OfInterestVar =<< liftIO (newVar HashMap.empty) addIdeGlobal . GarbageCollectVar =<< liftIO (newVar False) - defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \IsFileOfInterest f -> do + defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \IsFileOfInterest input -> do alwaysRerun filesOfInterest <- getFilesOfInterestUntracked - let foi = maybe NotFOI IsFOI $ f `HashMap.lookup` filesOfInterest + let rawFile = unInputPath input + let foi = maybe NotFOI IsFOI $ rawFile `HashMap.lookup` filesOfInterest fp = summarize foi res = (Just fp, Just foi) return res diff --git a/ghcide/src/Development/IDE/Core/RuleTypes.hs b/ghcide/src/Development/IDE/Core/RuleTypes.hs index d80e04d3c9..b4f57a40d5 100644 --- a/ghcide/src/Development/IDE/Core/RuleTypes.hs +++ b/ghcide/src/Development/IDE/Core/RuleTypes.hs @@ -291,7 +291,7 @@ type instance RuleInput GetModIfaceFromDiskAndIndex = ProjectHaskellFiles -- | Get a module interface details, either from an interface file or a typechecked module type instance RuleResult GetModIface = HiFileResult -type instance RuleInput GetModIface = AllHaskellFiles +type instance RuleInput GetModIface = ProjectHaskellFiles -- | Get the contents of a file, either dirty (if the buffer is modified) or Nothing to mean use from disk. type instance RuleResult GetFileContents = (FileVersion, Maybe Text) @@ -397,11 +397,11 @@ instance NFData ModSummaryResult where -- | Generate a ModSummary that has enough information to be used to get .hi and .hie files. -- without needing to parse the entire source type instance RuleResult GetModSummary = ModSummaryResult -type instance RuleInput GetModSummary = AllHaskellFiles +type instance RuleInput GetModSummary = ProjectHaskellFiles -- | Generate a ModSummary with the timestamps and preprocessed content elided, for more successful early cutoff type instance RuleResult GetModSummaryWithoutTimestamps = ModSummaryResult -type instance RuleInput GetModSummaryWithoutTimestamps = AllHaskellFiles +type instance RuleInput GetModSummaryWithoutTimestamps = ProjectHaskellFiles data GetParsedModule = GetParsedModule deriving (Eq, Show, Typeable, Generic) @@ -525,7 +525,7 @@ instance NFData AddWatchedFile -- thread killed exception issues, so we lift it to a full rule. -- https://github.com/digital-asset/daml/pull/2808#issuecomment-529639547 type instance RuleResult GhcSessionIO = IdeGhcSession -type instance RuleInput GhcSessionIO = ProjectHaskellFiles +type instance RuleInput GhcSessionIO = NoFile data IdeGhcSession = IdeGhcSession { loadSessionFun :: FilePath -> IO (IdeResult HscEnvEq, [FilePath]) diff --git a/ghcide/src/Development/IDE/Core/Rules.hs b/ghcide/src/Development/IDE/Core/Rules.hs index c285ca7f19..a866c365f0 100644 --- a/ghcide/src/Development/IDE/Core/Rules.hs +++ b/ghcide/src/Development/IDE/Core/Rules.hs @@ -126,7 +126,6 @@ import Development.IDE.GHC.Error import Development.IDE.GHC.Util hiding (modifyDynFlags) import Development.IDE.Graph -import Development.IDE.Graph.Internal.RuleInput import Development.IDE.Import.DependencyInformation import Development.IDE.Import.FindImports import qualified Development.IDE.Spans.AtPoint as AtPoint @@ -171,6 +170,7 @@ import System.Info.Extra (isWindows) import qualified Data.IntMap as IM import GHC.Fingerprint +import Development.IDE.Graph.Internal.Rules (InputClass(ProjectHaskellFiles, AllHaskellFiles, NoFile)) data Log @@ -228,14 +228,12 @@ getSourceFileSource nfp = do Just source -> pure $ T.encodeUtf8 source -- | Parse the contents of a haskell file. -getParsedModule :: IdeRule GetParsedModule i is ParsedModule - => InputPath i -> Action (Maybe ParsedModule) +getParsedModule :: InputPath AllHaskellFiles -> Action (Maybe ParsedModule) getParsedModule = use GetParsedModule -- | Parse the contents of a haskell file, -- ensuring comments are preserved in annotations -getParsedModuleWithComments :: IdeRule GetParsedModuleWithComments i is ParsedModule - => InputPath i -> Action (Maybe ParsedModule) +getParsedModuleWithComments :: InputPath AllHaskellFiles -> Action (Maybe ParsedModule) getParsedModuleWithComments = use GetParsedModuleWithComments ------------------------------------------------------------ @@ -371,9 +369,7 @@ execRawDepM act = -- | Given a target file path, construct the raw dependency results by following -- imports recursively. -rawDependencyInformation :: forall i is - . IdeRule GetModSummaryWithoutTimestamps i is ModSummaryResult - => [InputPath i] -> Action (RawDependencyInformation, BootIdMap) +rawDependencyInformation :: [InputPath AllHaskellFiles] -> Action (RawDependencyInformation, BootIdMap) rawDependencyInformation fs = do (rdi, ss) <- execRawDepM (goPlural fs) let bm = IntMap.foldrWithKey (updateBootMap rdi) IntMap.empty ss @@ -418,7 +414,7 @@ rawDependencyInformation fs = do (mns, ls) = unzip with_file -- Recursively process all the imports we just learnt about -- and get back a list of their FilePathIds - fids <- goPlural $ map (InputPath @i . artifactFilePath) ls + fids <- goPlural $ (classifyAllHaskellInputs . map artifactFilePath) ls -- Associate together the ModuleName with the FilePathId let moduleImports' = map (,Nothing) no_file ++ zip mns (map Just fids) -- Insert into the map the information about this modules @@ -527,8 +523,7 @@ persistentHieFileRule recorder = addPersistentRule GetHieAst $ \file -> runMaybe del = deltaFromDiff (T.decodeUtf8 $ Compat.hie_hs_src res) currentSource pure (HAR (Compat.hie_module res) (Compat.hie_asts res) refmap mempty (HieFromDisk res),del,ver) -getHieAstRuleDefinition :: IdeRule IsFileOfInterest i is IsFileOfInterestResult - => InputPath i -> HscEnv -> TcModuleResult -> Action (IdeResult HieAstResult) +getHieAstRuleDefinition :: InputPath AllHaskellFiles -> HscEnv -> TcModuleResult -> Action (IdeResult HieAstResult) getHieAstRuleDefinition f hsc tmr = do (diags, masts) <- liftIO $ generateHieAsts hsc tmr se <- getShakeExtras @@ -624,15 +619,13 @@ knownFilesRule recorder = defineEarlyCutOffNoFile (cmapWithPrio LogShake recorde getModuleGraphRule :: Recorder (WithPriority Log) -> Rules () getModuleGraphRule recorder = defineEarlyCutOffNoFile (cmapWithPrio LogShake recorder) $ \GetModuleGraph -> do fs <- toKnownFiles <$> useNoFile_ GetKnownTargets - dependencyInfoForFiles (map (InputPath @ProjectHaskellFile) $ HashSet.toList fs) + dependencyInfoForFiles (map (InputPath @ProjectHaskellFiles) $ HashSet.toList fs) -dependencyInfoForFiles :: forall i is - . IdeRule GetModSummaryWithoutTimestamps i is ModSummaryResult - => [InputPath i] -> Action (BS.ByteString, DependencyInformation) +dependencyInfoForFiles :: [InputPath AllHaskellFiles] -> Action (BS.ByteString, DependencyInformation) dependencyInfoForFiles fs = do (rawDepInfo, bm) <- rawDependencyInformation fs let (all_fs, _all_ids) = unzip $ HM.toList $ pathToIdMap $ rawPathIdMap rawDepInfo - msrs <- uses GetModSummaryWithoutTimestamps $ map (InputPath @i) all_fs + msrs <- uses GetModSummaryWithoutTimestamps $ classifyAllHaskellInputs all_fs let mss = map (fmap msrModSummary) msrs let deps = map (\i -> IM.lookup (getFilePathId i) (rawImports rawDepInfo)) _all_ids nodeKeys = IM.fromList $ catMaybes $ zipWith (\fi mms -> (getFilePathId fi,) . NodeKey_Module . msKey <$> mms) _all_ids mss @@ -710,7 +703,7 @@ loadGhcSession recorder ghcSessionDepsConfig = do let addDependency fp = do -- VSCode uses absolute paths in its filewatch notifications let nfp = toNormalizedFilePath' fp - itExists <- getFileExists nfp + itExists <- getFileExists $ InputPath nfp when itExists $ void $ do use_ GetModificationTime $ InputPath nfp mapM_ addDependency deps @@ -739,18 +732,18 @@ instance Default GhcSessionDepsConfig where ghcSessionDepsDefinition :: -- | full mod summary Bool -> - GhcSessionDepsConfig -> HscEnvEq -> InputPath i -> Action (Maybe HscEnvEq) -ghcSessionDepsDefinition fullModSummary GhcSessionDepsConfig{..} env file = do + GhcSessionDepsConfig -> HscEnvEq -> InputPath ProjectHaskellFiles -> Action (Maybe HscEnvEq) +ghcSessionDepsDefinition fullModSummary GhcSessionDepsConfig{..} env input = do let hsc = hscEnv env - mbdeps <- mapM(fmap artifactFilePath . snd) <$> use_ GetLocatedImports file + mbdeps <- mapM(fmap artifactFilePath . snd) <$> use_ GetLocatedImports input case mbdeps of Nothing -> return Nothing Just deps -> do - when fullModuleGraph $ void $ use_ ReportImportCycles file + when fullModuleGraph $ void $ use_ ReportImportCycles input ms <- msrModSummary <$> if fullModSummary - then use_ GetModSummary file - else use_ GetModSummaryWithoutTimestamps file + then use_ GetModSummary input + else use_ GetModSummaryWithoutTimestamps input depSessions <- map hscEnv <$> uses_ (GhcSessionDeps_ fullModSummary) (map InputPath deps) ifaces <- uses_ GetModIface $ map InputPath deps @@ -782,14 +775,14 @@ ghcSessionDepsDefinition fullModSummary GhcSessionDepsConfig{..} env file = do -- | Load a iface from disk, or generate it if there isn't one or it is out of date -- This rule also ensures that the `.hie` and `.o` (if needed) files are written out. getModIfaceFromDiskRule :: Recorder (WithPriority Log) -> Rules () -getModIfaceFromDiskRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleWithOldValue $ \GetModIfaceFromDisk f old -> do - ms <- msrModSummary <$> use_ GetModSummary f - mb_session <- use GhcSessionDeps f +getModIfaceFromDiskRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleWithOldValue $ \GetModIfaceFromDisk input old -> do + ms <- msrModSummary <$> use_ GetModSummary input + mb_session <- use GhcSessionDeps input case mb_session of Nothing -> return (Nothing, ([], Nothing)) Just session -> do - linkableType <- getLinkableType f - ver <- use_ GetModificationTime f + linkableType <- getLinkableType input + ver <- use_ GetModificationTime $ generalizeProjectInput input let m_old = case old of Shake.Succeeded (Just old_version) v -> Just (v, old_version) Shake.Stale _ (Just old_version) v -> Just (v, old_version) @@ -799,7 +792,7 @@ getModIfaceFromDiskRule recorder = defineEarlyCutoff (cmapWithPrio LogShake reco , old_value = m_old , get_file_version = use GetModificationTime_{missingFileDiagnostics = False} . InputPath , get_linkable_hashes = \fs -> map (snd . fromJust . hirCoreFp) <$> uses_ GetModIface (map InputPath fs) - , regenerate = regenerateHiFile session (unInputPath f) ms + , regenerate = regenerateHiFile session (unInputPath input) ms } r <- loadInterface (hscEnv session) ms linkableType recompInfo case r of @@ -819,15 +812,15 @@ getModIfaceFromDiskRule recorder = defineEarlyCutoff (cmapWithPrio LogShake reco getModIfaceFromDiskAndIndexRule :: Recorder (WithPriority Log) -> Rules () getModIfaceFromDiskAndIndexRule recorder = -- doesn't need early cutoff since all its dependencies already have it - defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \GetModIfaceFromDiskAndIndex f -> do - x <- use_ GetModIfaceFromDisk f + defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \GetModIfaceFromDiskAndIndex input -> do + x <- use_ GetModIfaceFromDisk input se@ShakeExtras{withHieDb} <- getShakeExtras -- GetModIfaceFromDisk should have written a `.hie` file, must check if it matches version in db let ms = hirModSummary x hie_loc = Compat.ml_hie_file $ ms_location ms fileHash <- liftIO $ Util.getFileHash hie_loc - mrow <- liftIO $ withHieDb (\hieDb -> HieDb.lookupHieFileFromSource hieDb (fromNormalizedFilePath $ unInputPath f)) + mrow <- liftIO $ withHieDb (\hieDb -> HieDb.lookupHieFileFromSource hieDb (fromNormalizedFilePath $ unInputPath input)) let hie_loc' = HieDb.hieModuleHieFile <$> mrow case mrow of Just row @@ -837,7 +830,7 @@ getModIfaceFromDiskAndIndexRule recorder = -- All good, the db has indexed the file when (coerce $ ideTesting se) $ liftIO $ mRunLspT (lspEnv se) $ LSP.sendNotification (SMethod_CustomMethod (Proxy @"ghcide/reference/ready")) $ - toJSON $ fromNormalizedFilePath $ unInputPath f + toJSON $ fromNormalizedFilePath $ unInputPath input -- Not in db, must re-index _ -> do ehf <- liftIO $ runIdeAction "GetModIfaceFromDiskAndIndex" se $ runExceptT $ @@ -847,8 +840,8 @@ getModIfaceFromDiskAndIndexRule recorder = Left err -> fail $ "failed to read .hie file " ++ show hie_loc ++ ": " ++ displayException err -- can just re-index the file we read from disk Right hf -> liftIO $ do - logWith recorder Logger.Debug $ LogReindexingHieFile $ unInputPath f - indexHieFile se ms (unInputPath f) fileHash hf + logWith recorder Logger.Debug $ LogReindexingHieFile $ unInputPath input + indexHieFile se ms (unInputPath input) fileHash hf return (Just x) @@ -866,12 +859,12 @@ getModSummaryRule displayTHWarning recorder = do logItOnce <- liftIO $ once $ putStrLn "" addIdeGlobal (DisplayTHWarning logItOnce) - defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \GetModSummary f -> do - session' <- hscEnv <$> use_ GhcSession f + defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \GetModSummary input -> do + session' <- hscEnv <$> use_ GhcSession input modify_dflags <- getModifyDynFlags dynFlagsModifyGlobal let session = hscSetFlags (modify_dflags $ hsc_dflags session') session' - (modTime, mFileContent) <- getFileContents f - let fp = fromNormalizedFilePath f + (modTime, mFileContent) <- getFileContents $ generalizeProjectInput input + let fp = fromNormalizedFilePath $ unInputPath input modS <- liftIO $ runExceptT $ getModSummaryFromImports session fp modTime (textToStringBuffer <$> mFileContent) case modS of @@ -886,8 +879,8 @@ getModSummaryRule displayTHWarning recorder = do return ( Just (fingerprintToBS fingerPrint) , ([], Just res)) Left diags -> return (Nothing, (diags, Nothing)) - defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \GetModSummaryWithoutTimestamps f -> do - mbMs <- use GetModSummary f + defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \GetModSummaryWithoutTimestamps input -> do + mbMs <- use GetModSummary input case mbMs of Just res@ModSummaryResult{..} -> do let ms = msrModSummary { @@ -897,10 +890,10 @@ getModSummaryRule displayTHWarning recorder = do return (Just fp, Just res{msrModSummary = ms}) Nothing -> return (Nothing, Nothing) -generateCore :: RunSimplifier -> NormalizedFilePath -> Action (IdeResult ModGuts) -generateCore runSimplifier file = do - packageState <- hscEnv <$> use_ GhcSessionDeps file - tm <- use_ TypeCheck file +generateCore :: RunSimplifier -> InputPath ProjectHaskellFiles -> Action (IdeResult ModGuts) +generateCore runSimplifier input = do + packageState <- hscEnv <$> use_ GhcSessionDeps input + tm <- use_ TypeCheck input liftIO $ compileModule runSimplifier packageState (tmrModSummary tm) (tmrTypechecked tm) generateCoreRule :: Recorder (WithPriority Log) -> Rules () @@ -908,15 +901,15 @@ generateCoreRule recorder = define (cmapWithPrio LogShake recorder) $ \GenerateCore -> generateCore (RunSimplifier True) getModIfaceRule :: Recorder (WithPriority Log) -> Rules () -getModIfaceRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \GetModIface f -> do - fileOfInterest <- use_ IsFileOfInterest f +getModIfaceRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \GetModIface input -> do + fileOfInterest <- use_ IsFileOfInterest $ generalizeProjectInput input res <- case fileOfInterest of IsFOI status -> do -- Never load from disk for files of interest - tmr <- use_ TypeCheck f - linkableType <- getLinkableType f - hsc <- hscEnv <$> use_ GhcSessionDeps f - let compile = fmap ([],) $ use GenerateCore f + tmr <- use_ TypeCheck input + linkableType <- getLinkableType input + hsc <- hscEnv <$> use_ GhcSessionDeps input + let compile = fmap ([],) $ use GenerateCore input se <- getShakeExtras (diags, !mbHiFile) <- writeCoreFileIfNeeded se hsc linkableType compile tmr let fp = hiFileFingerPrint <$> mbHiFile @@ -927,7 +920,7 @@ getModIfaceRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ _ -> pure [] return (fp, (diags++hiDiags, mbHiFile)) NotFOI -> do - hiFile <- use GetModIfaceFromDiskAndIndex f + hiFile <- use GetModIfaceFromDiskAndIndex input let fp = hiFileFingerPrint <$> hiFile return (fp, ([], hiFile)) @@ -1098,21 +1091,21 @@ getLinkableRule recorder = return (fileHash <$ hmi, (warns, LinkableResult <$> hmi <*> pure fileHash)) -- | For now we always use bytecode unless something uses unboxed sums and tuples along with TH -getLinkableType :: InputPath i -> Action (Maybe LinkableType) +getLinkableType :: InputPath ProjectHaskellFiles -> Action (Maybe LinkableType) getLinkableType f = use_ NeedsCompilation f -needsCompilationRule :: NormalizedFilePath -> Action (IdeResultNoDiagnosticsEarlyCutoff (Maybe LinkableType)) -needsCompilationRule file - | "boot" `isSuffixOf` fromNormalizedFilePath file = +needsCompilationRule :: InputPath ProjectHaskellFiles -> Action (IdeResultNoDiagnosticsEarlyCutoff (Maybe LinkableType)) +needsCompilationRule input + | "boot" `isSuffixOf` fromNormalizedFilePath (unInputPath input) = pure (Just $ encodeLinkableType Nothing, Just Nothing) -needsCompilationRule file = do +needsCompilationRule input = do graph <- useNoFile GetModuleGraph res <- case graph of -- Treat as False if some reverse dependency header fails to parse Nothing -> pure Nothing - Just depinfo -> case immediateReverseDependencies file depinfo of + Just depinfo -> case immediateReverseDependencies input depinfo of -- If we fail to get immediate reverse dependencies, fail with an error message - Nothing -> fail $ "Failed to get the immediate reverse dependencies of " ++ show file + Nothing -> fail $ "Failed to get the immediate reverse dependencies of " ++ show (unInputPath input) Just revdeps -> do -- It's important to use stale data here to avoid wasted work. -- if NeedsCompilation fails for a module M its result will be under-approximated @@ -1208,8 +1201,8 @@ mainRule recorder RulesConfig{..} = do -- * Object/BCO -> NoLinkable : the prev linkable can be ignored, signal "no change" -- * otherwise : the prev linkable cannot be reused, signal "value has changed" if enableTemplateHaskell - then defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleWithCustomNewnessCheck (<=) $ \NeedsCompilation file -> - needsCompilationRule file + then defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleWithCustomNewnessCheck (<=) $ \NeedsCompilation input -> + needsCompilationRule input else defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \NeedsCompilation _ -> return $ Just Nothing generateCoreRule recorder getImportMapRule recorder @@ -1219,13 +1212,13 @@ mainRule recorder RulesConfig{..} = do getLinkableRule recorder -- | Get HieFile for haskell file on NormalizedFilePath -getHieFile :: NormalizedFilePath -> Action (Maybe HieFile) -getHieFile nfp = runMaybeT $ do - HAR {hieAst} <- MaybeT $ use GetHieAst nfp - tmr <- MaybeT $ use TypeCheck nfp - ghc <- MaybeT $ use GhcSession nfp - msr <- MaybeT $ use GetModSummaryWithoutTimestamps nfp - source <- lift $ getSourceFileSource nfp +getHieFile :: InputPath ProjectHaskellFiles -> Action (Maybe HieFile) +getHieFile input = runMaybeT $ do + HAR {hieAst} <- MaybeT $ use GetHieAst $ generalizeProjectInput input + tmr <- MaybeT $ use TypeCheck input + ghc <- MaybeT $ use GhcSession input + msr <- MaybeT $ use GetModSummaryWithoutTimestamps input + source <- lift $ getSourceFileSource $ unInputPath input let exports = tcg_exports $ tmrTypechecked tmr typedAst <- MaybeT $ pure $ cast hieAst liftIO $ runHsc (hscEnv ghc) $ mkHieFile' (msrModSummary msr) exports typedAst source diff --git a/ghcide/src/Development/IDE/Import/DependencyInformation.hs b/ghcide/src/Development/IDE/Import/DependencyInformation.hs index 5372a1364a..697dcb9e0c 100644 --- a/ghcide/src/Development/IDE/Import/DependencyInformation.hs +++ b/ghcide/src/Development/IDE/Import/DependencyInformation.hs @@ -49,11 +49,13 @@ import Data.Maybe import Data.Tuple.Extra hiding (first, second) import Development.IDE.GHC.Compat import Development.IDE.GHC.Orphans () +import Development.IDE.Graph.Internal.Rules import Development.IDE.Import.FindImports (ArtifactsLocation (..)) import Development.IDE.Types.Diagnostics import Development.IDE.Types.Location import GHC.Generics (Generic) import Prelude hiding (mod) +import Development.IDE.Core.InputPath (InputPath(..)) -- | The imports for a given module. @@ -335,10 +337,10 @@ transitiveReverseDependencies file DependencyInformation{..} = do in IntSet.foldr go res new -- | Immediate reverse dependencies of a file -immediateReverseDependencies :: NormalizedFilePath -> DependencyInformation -> Maybe [NormalizedFilePath] -immediateReverseDependencies file DependencyInformation{..} = do - FilePathId cur_id <- lookupPathToId depPathIdMap file - return $ map (idToPath depPathIdMap . FilePathId) (maybe mempty IntSet.toList (IntMap.lookup cur_id depReverseModuleDeps)) +immediateReverseDependencies :: InputPath ProjectHaskellFiles -> DependencyInformation -> Maybe [InputPath ProjectHaskellFiles] +immediateReverseDependencies input DependencyInformation{..} = do + FilePathId cur_id <- lookupPathToId depPathIdMap $ unInputPath input + return $ map (InputPath . idToPath depPathIdMap . FilePathId) (maybe mempty IntSet.toList (IntMap.lookup cur_id depReverseModuleDeps)) -- | returns all transitive dependencies in topological order. transitiveDeps :: DependencyInformation -> NormalizedFilePath -> Maybe TransitiveDependencies From 24c019fda7bb2a6a255d6b47e418e96f216c7f63 Mon Sep 17 00:00:00 2001 From: Elodie Lander Date: Mon, 18 Nov 2024 22:27:01 -0500 Subject: [PATCH 12/14] WIP fix Rules --- ghcide/src/Development/IDE/Core/RuleTypes.hs | 2 +- ghcide/src/Development/IDE/Core/Rules.hs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ghcide/src/Development/IDE/Core/RuleTypes.hs b/ghcide/src/Development/IDE/Core/RuleTypes.hs index b4f57a40d5..ce457f38af 100644 --- a/ghcide/src/Development/IDE/Core/RuleTypes.hs +++ b/ghcide/src/Development/IDE/Core/RuleTypes.hs @@ -66,7 +66,7 @@ encodeLinkableType (Just ObjectLinkable) = "2" -- | The parse tree for the file using GetFileContents type instance RuleResult GetParsedModule = ParsedModule -type instance RuleInput GetParsedModule = AllHaskellFiles +type instance RuleInput GetParsedModule = ProjectHaskellFiles -- | The parse tree for the file using GetFileContents, -- all comments included using Opt_KeepRawTokenStream diff --git a/ghcide/src/Development/IDE/Core/Rules.hs b/ghcide/src/Development/IDE/Core/Rules.hs index a866c365f0..deccb62add 100644 --- a/ghcide/src/Development/IDE/Core/Rules.hs +++ b/ghcide/src/Development/IDE/Core/Rules.hs @@ -369,7 +369,7 @@ execRawDepM act = -- | Given a target file path, construct the raw dependency results by following -- imports recursively. -rawDependencyInformation :: [InputPath AllHaskellFiles] -> Action (RawDependencyInformation, BootIdMap) +rawDependencyInformation :: [InputPath ProjectHaskellFiles] -> Action (RawDependencyInformation, BootIdMap) rawDependencyInformation fs = do (rdi, ss) <- execRawDepM (goPlural fs) let bm = IntMap.foldrWithKey (updateBootMap rdi) IntMap.empty ss @@ -602,7 +602,7 @@ typeCheckRule :: Recorder (WithPriority Log) -> Rules () typeCheckRule recorder = define (cmapWithPrio LogShake recorder) $ \TypeCheck file -> do pm <- use_ GetParsedModule file hsc <- hscEnv <$> use_ GhcSessionDeps file - foi <- use_ IsFileOfInterest file + foi <- use_ IsFileOfInterest $ generalizeProjectInput file -- We should only call the typecheck rule for files of interest. -- Keeping typechecked modules in memory for other files is -- very expensive. @@ -621,11 +621,11 @@ getModuleGraphRule recorder = defineEarlyCutOffNoFile (cmapWithPrio LogShake rec fs <- toKnownFiles <$> useNoFile_ GetKnownTargets dependencyInfoForFiles (map (InputPath @ProjectHaskellFiles) $ HashSet.toList fs) -dependencyInfoForFiles :: [InputPath AllHaskellFiles] -> Action (BS.ByteString, DependencyInformation) +dependencyInfoForFiles :: [InputPath ProjectHaskellFiles] -> Action (BS.ByteString, DependencyInformation) dependencyInfoForFiles fs = do (rawDepInfo, bm) <- rawDependencyInformation fs let (all_fs, _all_ids) = unzip $ HM.toList $ pathToIdMap $ rawPathIdMap rawDepInfo - msrs <- uses GetModSummaryWithoutTimestamps $ classifyAllHaskellInputs all_fs + msrs <- uses GetModSummaryWithoutTimestamps $ classifyProjectHaskellInputs all_fs let mss = map (fmap msrModSummary) msrs let deps = map (\i -> IM.lookup (getFilePathId i) (rawImports rawDepInfo)) _all_ids nodeKeys = IM.fromList $ catMaybes $ zipWith (\fi mms -> (getFilePathId fi,) . NodeKey_Module . msKey <$> mms) _all_ids mss From 9acb166faaeedf9a000d3e915d4846689be4fee6 Mon Sep 17 00:00:00 2001 From: Elodie Lander Date: Wed, 27 Nov 2024 08:16:45 -0500 Subject: [PATCH 13/14] WIP Fix core type errors --- ghcide/src/Development/IDE/Core/Actions.hs | 33 ++++++--- .../src/Development/IDE/Core/PluginUtils.hs | 22 +++--- ghcide/src/Development/IDE/Core/RuleTypes.hs | 2 +- ghcide/src/Development/IDE/Core/Rules.hs | 70 +++++++++++++------ ghcide/src/Development/IDE/LSP/Outline.hs | 3 +- ghcide/src/Development/IDE/Plugin/Test.hs | 12 ++-- ghcide/src/Development/IDE/Spans/AtPoint.hs | 43 ++++++++---- 7 files changed, 122 insertions(+), 63 deletions(-) diff --git a/ghcide/src/Development/IDE/Core/Actions.hs b/ghcide/src/Development/IDE/Core/Actions.hs index 20c86c8280..d7bbef309b 100644 --- a/ghcide/src/Development/IDE/Core/Actions.hs +++ b/ghcide/src/Development/IDE/Core/Actions.hs @@ -32,6 +32,7 @@ import Language.LSP.Protocol.Types (DocumentHighlight (..), SymbolInformation (..), normalizedFilePathToUri, uriToNormalizedFilePath) +import Development.IDE.Core.InputPath (classifyProjectHaskellInputs, InputPath (InputPath)) -- | Eventually this will lookup/generate URIs for files in dependencies, but not in the @@ -59,12 +60,22 @@ getAtPoint file pos = runMaybeT $ do ide <- ask opts <- liftIO $ getIdeOptionsIO ide - (hf, mapping) <- useWithStaleFastMT GetHieAst file - env <- hscEnv . fst <$> useWithStaleFastMT GhcSession file - dkMap <- lift $ maybe (DKMap mempty mempty) fst <$> runMaybeT (useWithStaleFastMT GetDocMap file) + (hf, mapping) <- useWithStaleFastMT GetHieAst $ InputPath file + -- The HscEnv and DKMap are not strictly necessary for hover + -- to work, so we only calculate them for project files, not + -- for dependency files. They provide information that will + -- not be displayed in dependency files. See the atPoint + -- function in ghcide/src/Development/IDE/Spans/AtPoint.hs + -- for the specifics of how they are used. + (mEnv, mDkMap) <- case classifyProjectHaskellInputs [file] of + [] -> pure (Nothing, Nothing) + projectInput:_ -> do + env <- hscEnv . fst <$> useWithStaleFastMT GhcSession projectInput + dkMap <- lift $ maybe (DKMap mempty mempty) fst <$> runMaybeT (useWithStaleFastMT GetDocMap projectInput) + pure (Just env, Just dkMap) !pos' <- MaybeT (return $ fromCurrentPosition mapping pos) - MaybeT $ liftIO $ fmap (first (toCurrentRange mapping =<<)) <$> AtPoint.atPoint opts hf dkMap env pos' + MaybeT $ liftIO $ fmap (first (toCurrentRange mapping =<<)) <$> AtPoint.atPoint opts hf mDkMap mEnv pos' -- | Converts locations in the source code to their current positions, -- taking into account changes that may have occurred due to edits. @@ -87,7 +98,7 @@ toCurrentLocation mapping file (Location uri range) = else do otherLocationMapping <- fmap (fmap snd) $ runMaybeT $ do otherLocationFile <- MaybeT $ pure $ uriToNormalizedFilePath nUri - useWithStaleFastMT GetHieAst otherLocationFile + useWithStaleFastMT GetHieAst $ InputPath otherLocationFile pure $ Location uri <$> (flip toCurrentRange range =<< otherLocationMapping) where nUri :: NormalizedUri @@ -98,8 +109,10 @@ getDefinition :: NormalizedFilePath -> Position -> IdeAction (Maybe [(Location, getDefinition file pos = runMaybeT $ do ide@ShakeExtras{ withHieDb, hiedbWriter } <- ask opts <- liftIO $ getIdeOptionsIO ide - (HAR _ hf _ _ _, mapping) <- useWithStaleFastMT GetHieAst file - (ImportMap imports, _) <- useWithStaleFastMT GetImportMap file + (HAR _ hf _ _ _, mapping) <- useWithStaleFastMT GetHieAst $ InputPath file + (ImportMap imports, _) <- case classifyProjectHaskellInputs [file] of + [] -> pure (ImportMap mempty, PositionMapping idDelta) + (projectInput: _) -> useWithStaleFastMT GetImportMap projectInput !pos' <- MaybeT (pure $ fromCurrentPosition mapping pos) locationsWithIdentifier <- AtPoint.gotoDefinition withHieDb (lookupMod hiedbWriter) opts imports hf pos' mapMaybeM (\(location, identifier) -> do @@ -112,7 +125,7 @@ getTypeDefinition :: NormalizedFilePath -> Position -> IdeAction (Maybe [(Locati getTypeDefinition file pos = runMaybeT $ do ide@ShakeExtras{ withHieDb, hiedbWriter } <- ask opts <- liftIO $ getIdeOptionsIO ide - (hf, mapping) <- useWithStaleFastMT GetHieAst file + (hf, mapping) <- useWithStaleFastMT GetHieAst $ InputPath file !pos' <- MaybeT (return $ fromCurrentPosition mapping pos) locationsWithIdentifier <- AtPoint.gotoTypeDefinition withHieDb (lookupMod hiedbWriter) opts hf pos' mapMaybeM (\(location, identifier) -> do @@ -122,7 +135,7 @@ getTypeDefinition file pos = runMaybeT $ do highlightAtPoint :: NormalizedFilePath -> Position -> IdeAction (Maybe [DocumentHighlight]) highlightAtPoint file pos = runMaybeT $ do - (HAR _ hf rf _ _,mapping) <- useWithStaleFastMT GetHieAst file + (HAR _ hf rf _ _,mapping) <- useWithStaleFastMT GetHieAst $ InputPath file !pos' <- MaybeT (return $ fromCurrentPosition mapping pos) let toCurrentHighlight (DocumentHighlight range t) = flip DocumentHighlight t <$> toCurrentRange mapping range mapMaybe toCurrentHighlight <$>AtPoint.documentHighlight hf rf pos' @@ -132,7 +145,7 @@ refsAtPoint :: NormalizedFilePath -> Position -> Action [Location] refsAtPoint file pos = do ShakeExtras{withHieDb} <- getShakeExtras fs <- HM.keys <$> getFilesOfInterestUntracked - asts <- HM.fromList . mapMaybe sequence . zip fs <$> usesWithStale GetHieAst fs + asts <- HM.fromList . mapMaybe sequence . zip fs <$> usesWithStale GetHieAst (map InputPath fs) AtPoint.referencesAtPoint withHieDb file pos (AtPoint.FOIReferences asts) workspaceSymbols :: T.Text -> IdeAction (Maybe [SymbolInformation]) diff --git a/ghcide/src/Development/IDE/Core/PluginUtils.hs b/ghcide/src/Development/IDE/Core/PluginUtils.hs index 76c88421c9..d0335f01dd 100644 --- a/ghcide/src/Development/IDE/Core/PluginUtils.hs +++ b/ghcide/src/Development/IDE/Core/PluginUtils.hs @@ -40,11 +40,11 @@ import Development.IDE.Core.Shake (IdeAction, IdeRule, import qualified Development.IDE.Core.Shake as Shake import Development.IDE.GHC.Orphans () import Development.IDE.Graph hiding (ShakeValue) -import Development.IDE.Types.Location (NormalizedFilePath) import qualified Development.IDE.Types.Location as Location import qualified Ide.Logger as Logger import Ide.Plugin.Error import qualified Language.LSP.Protocol.Types as LSP +import Development.IDE.Core.InputPath (InputPath) -- ---------------------------------------------------------------------------- -- Action wrappers @@ -63,30 +63,30 @@ runActionMT herald ide act = join $ shakeEnqueue (shakeExtras ide) (mkDelayedAction herald Logger.Debug $ runMaybeT act) -- |ExceptT version of `use` that throws a PluginRuleFailed upon failure -useE :: IdeRule k v => k -> NormalizedFilePath -> ExceptT PluginError Action v +useE :: IdeRule k i v => k -> InputPath i -> ExceptT PluginError Action v useE k = maybeToExceptT (PluginRuleFailed (T.pack $ show k)) . useMT k -- |MaybeT version of `use` -useMT :: IdeRule k v => k -> NormalizedFilePath -> MaybeT Action v +useMT :: IdeRule k i v => k -> InputPath i -> MaybeT Action v useMT k = MaybeT . Shake.use k -- |ExceptT version of `uses` that throws a PluginRuleFailed upon failure -usesE :: (Traversable f, IdeRule k v) => k -> f NormalizedFilePath -> ExceptT PluginError Action (f v) +usesE :: (Traversable f, IdeRule k i v) => k -> f (InputPath i) -> ExceptT PluginError Action (f v) usesE k = maybeToExceptT (PluginRuleFailed (T.pack $ show k)) . usesMT k -- |MaybeT version of `uses` -usesMT :: (Traversable f, IdeRule k v) => k -> f NormalizedFilePath -> MaybeT Action (f v) +usesMT :: (Traversable f, IdeRule k i v) => k -> f (InputPath i) -> MaybeT Action (f v) usesMT k xs = MaybeT $ sequence <$> Shake.uses k xs -- |ExceptT version of `useWithStale` that throws a PluginRuleFailed upon -- failure -useWithStaleE :: IdeRule k v - => k -> NormalizedFilePath -> ExceptT PluginError Action (v, PositionMapping) +useWithStaleE :: IdeRule k i v + => k -> InputPath i -> ExceptT PluginError Action (v, PositionMapping) useWithStaleE key = maybeToExceptT (PluginRuleFailed (T.pack $ show key)) . useWithStaleMT key -- |MaybeT version of `useWithStale` -useWithStaleMT :: IdeRule k v - => k -> NormalizedFilePath -> MaybeT Action (v, PositionMapping) +useWithStaleMT :: IdeRule k i v + => k -> InputPath i -> MaybeT Action (v, PositionMapping) useWithStaleMT key file = MaybeT $ runIdentity <$> Shake.usesWithStale key (Identity file) -- ---------------------------------------------------------------------------- @@ -103,11 +103,11 @@ runIdeActionMT _herald s i = MaybeT $ liftIO $ runReaderT (Shake.runIdeActionT $ -- |ExceptT version of `useWithStaleFast` that throws a PluginRuleFailed upon -- failure -useWithStaleFastE :: IdeRule k v => k -> NormalizedFilePath -> ExceptT PluginError IdeAction (v, PositionMapping) +useWithStaleFastE :: IdeRule k i v => k -> InputPath i -> ExceptT PluginError IdeAction (v, PositionMapping) useWithStaleFastE k = maybeToExceptT (PluginRuleFailed (T.pack $ show k)) . useWithStaleFastMT k -- |MaybeT version of `useWithStaleFast` -useWithStaleFastMT :: IdeRule k v => k -> NormalizedFilePath -> MaybeT IdeAction (v, PositionMapping) +useWithStaleFastMT :: IdeRule k i v => k -> InputPath i -> MaybeT IdeAction (v, PositionMapping) useWithStaleFastMT k = MaybeT . Shake.useWithStaleFast k -- ---------------------------------------------------------------------------- diff --git a/ghcide/src/Development/IDE/Core/RuleTypes.hs b/ghcide/src/Development/IDE/Core/RuleTypes.hs index ce457f38af..cd08b7741c 100644 --- a/ghcide/src/Development/IDE/Core/RuleTypes.hs +++ b/ghcide/src/Development/IDE/Core/RuleTypes.hs @@ -71,7 +71,7 @@ type instance RuleInput GetParsedModule = ProjectHaskellFiles -- | The parse tree for the file using GetFileContents, -- all comments included using Opt_KeepRawTokenStream type instance RuleResult GetParsedModuleWithComments = ParsedModule -type instance RuleInput GetParsedModuleWithComments = AllHaskellFiles +type instance RuleInput GetParsedModuleWithComments = ProjectHaskellFiles type instance RuleResult GetModuleGraph = DependencyInformation type instance RuleInput GetModuleGraph = NoFile diff --git a/ghcide/src/Development/IDE/Core/Rules.hs b/ghcide/src/Development/IDE/Core/Rules.hs index deccb62add..c7c2558e49 100644 --- a/ghcide/src/Development/IDE/Core/Rules.hs +++ b/ghcide/src/Development/IDE/Core/Rules.hs @@ -170,7 +170,7 @@ import System.Info.Extra (isWindows) import qualified Data.IntMap as IM import GHC.Fingerprint -import Development.IDE.Graph.Internal.Rules (InputClass(ProjectHaskellFiles, AllHaskellFiles, NoFile)) +import Development.IDE.Graph.Internal.Rules (InputClass(ProjectHaskellFiles)) data Log @@ -222,18 +222,18 @@ toIdeResult = either (, Nothing) (([],) . Just) -- TODO: return text --> return rope getSourceFileSource :: NormalizedFilePath -> Action BS.ByteString getSourceFileSource nfp = do - (_, msource) <- getFileContents nfp + (_, msource) <- getFileContents $ InputPath nfp case msource of Nothing -> liftIO $ BS.readFile (fromNormalizedFilePath nfp) Just source -> pure $ T.encodeUtf8 source -- | Parse the contents of a haskell file. -getParsedModule :: InputPath AllHaskellFiles -> Action (Maybe ParsedModule) +getParsedModule :: InputPath ProjectHaskellFiles -> Action (Maybe ParsedModule) getParsedModule = use GetParsedModule -- | Parse the contents of a haskell file, -- ensuring comments are preserved in annotations -getParsedModuleWithComments :: InputPath AllHaskellFiles -> Action (Maybe ParsedModule) +getParsedModuleWithComments :: InputPath ProjectHaskellFiles -> Action (Maybe ParsedModule) getParsedModuleWithComments = use GetParsedModuleWithComments ------------------------------------------------------------ @@ -323,16 +323,16 @@ getLocatedImportsRule recorder = let getTargetFor modName nfp | Just (TargetFile nfp') <- HM.lookup (TargetFile nfp) targetsMap = do -- reuse the existing NormalizedFilePath in order to maximize sharing - itExists <- getFileExists nfp' + itExists <- getFileExists $ InputPath nfp' return $ if itExists then Just nfp' else Nothing | Just tt <- HM.lookup (TargetModule modName) targets = do -- reuse the existing NormalizedFilePath in order to maximize sharing let ttmap = HM.mapWithKey const (HashSet.toMap tt) nfp' = HM.lookupDefault nfp nfp ttmap - itExists <- getFileExists nfp' + itExists <- getFileExists $ InputPath nfp' return $ if itExists then Just nfp' else Nothing | otherwise = do - itExists <- getFileExists nfp + itExists <- getFileExists $ InputPath nfp return $ if itExists then Just nfp else Nothing (diags, imports') <- fmap unzip $ forM imports $ \(isSource, (mbPkgName, modName)) -> do diagOrImp <- locateModule (hscSetFlags dflags env) import_dirs (optExtensions opt) getTargetFor modName mbPkgName isSource @@ -379,7 +379,7 @@ rawDependencyInformation fs = do mss <- lift $ (fmap.fmap) msrModSummary <$> uses GetModSummaryWithoutTimestamps ff zipWithM go ff mss - go :: InputPath i -- ^ Current module being processed + go :: InputPath ProjectHaskellFiles -- ^ Current module being processed -> Maybe ModSummary -- ^ ModSummary of the module -> RawDepM FilePathId go f mbModSum = do @@ -414,7 +414,7 @@ rawDependencyInformation fs = do (mns, ls) = unzip with_file -- Recursively process all the imports we just learnt about -- and get back a list of their FilePathIds - fids <- goPlural $ (classifyAllHaskellInputs . map artifactFilePath) ls + fids <- goPlural $ (classifyProjectHaskellInputs . map artifactFilePath) ls -- Associate together the ModuleName with the FilePathId let moduleImports' = map (,Nothing) no_file ++ zip mns (map Just fids) -- Insert into the map the information about this modules @@ -506,10 +506,37 @@ reportImportCyclesRule recorder = getHieAstsRule :: Recorder (WithPriority Log) -> Rules () getHieAstsRule recorder = - define (cmapWithPrio LogShake recorder) $ \GetHieAst f -> do - tmr <- use_ TypeCheck f - hsc <- hscEnv <$> use_ GhcSessionDeps f - getHieAstRuleDefinition f hsc tmr + define (cmapWithPrio LogShake recorder) $ \GetHieAst input -> do + let file = unInputPath input + case classifyProjectHaskellInputs [file] of + [] -> do + se <- getShakeExtras + mHieFile <- liftIO + $ runIdeAction "GetHieAst" se + $ runMaybeT + -- We can look up the HIE file from its source + -- because at this point lookupMod has already been + -- called and has created the the source file in + -- the .hls directory and indexed it. + $ readHieFileForSrcFromDisk recorder file + pure ([], makeHieAstResult <$> mHieFile) + projectInput:_ -> do + tmr <- use_ TypeCheck projectInput + hsc <- hscEnv <$> use_ GhcSessionDeps projectInput + getHieAstRuleDefinition projectInput hsc tmr + where + -- Make an HieAstResult from a loaded HieFile + makeHieAstResult :: HieFile -> HieAstResult + makeHieAstResult hieFile = + HAR + (hie_module hieFile) + hieAsts + (generateReferencesMap $ M.elems $ getAsts hieAsts) + mempty + (HieFromDisk hieFile) + where + hieAsts :: HieASTs TypeIndex + hieAsts = hie_asts hieFile persistentHieFileRule :: Recorder (WithPriority Log) -> Rules () persistentHieFileRule recorder = addPersistentRule GetHieAst $ \file -> runMaybeT $ do @@ -523,23 +550,24 @@ persistentHieFileRule recorder = addPersistentRule GetHieAst $ \file -> runMaybe del = deltaFromDiff (T.decodeUtf8 $ Compat.hie_hs_src res) currentSource pure (HAR (Compat.hie_module res) (Compat.hie_asts res) refmap mempty (HieFromDisk res),del,ver) -getHieAstRuleDefinition :: InputPath AllHaskellFiles -> HscEnv -> TcModuleResult -> Action (IdeResult HieAstResult) -getHieAstRuleDefinition f hsc tmr = do +getHieAstRuleDefinition :: InputPath ProjectHaskellFiles -> HscEnv -> TcModuleResult -> Action (IdeResult HieAstResult) +getHieAstRuleDefinition input hsc tmr = do + let file = unInputPath input (diags, masts) <- liftIO $ generateHieAsts hsc tmr se <- getShakeExtras - isFoi <- use_ IsFileOfInterest f + isFoi <- use_ IsFileOfInterest $ generalizeProjectInput input diagsWrite <- case isFoi of IsFOI Modified{firstOpen = False} -> do when (coerce $ ideTesting se) $ liftIO $ mRunLspT (lspEnv se) $ LSP.sendNotification (SMethod_CustomMethod (Proxy @"ghcide/reference/ready")) $ - toJSON $ fromNormalizedFilePath $ unInputPath f + toJSON $ fromNormalizedFilePath file pure [] _ | Just asts <- masts -> do - source <- getSourceFileSource $ unInputPath f + source <- getSourceFileSource file let exports = tcg_exports $ tmrTypechecked tmr modSummary = tmrModSummary tmr - liftIO $ writeAndIndexHieFile hsc se modSummary (unInputPath f) exports asts source + liftIO $ writeAndIndexHieFile hsc se modSummary file exports asts source _ -> pure [] let refmap = Compat.generateReferencesMap . Compat.getAsts <$> masts @@ -559,7 +587,7 @@ persistentImportMapRule = addPersistentRule GetImportMap $ \_ -> pure $ Just (Im getBindingsRule :: Recorder (WithPriority Log) -> Rules () getBindingsRule recorder = define (cmapWithPrio LogShake recorder) $ \GetBindings f -> do - HAR{hieKind=kind, refMap=rm} <- use_ GetHieAst f + HAR{hieKind=kind, refMap=rm} <- use_ GetHieAst $ generalizeProjectInput f case kind of HieFresh -> pure ([], Just $ bindings rm) HieFromDisk _ -> pure ([], Nothing) @@ -571,7 +599,7 @@ getDocMapRule recorder = -- but we never generated a DocMap for it (tmrTypechecked -> tc, _) <- useWithStale_ TypeCheck file (hscEnv -> hsc, _) <- useWithStale_ GhcSessionDeps file - (HAR{refMap=rf}, _) <- useWithStale_ GetHieAst file + (HAR{refMap=rf}, _) <- useWithStale_ GetHieAst $ generalizeProjectInput file dkMap <- liftIO $ mkDocMap hsc rf tc return ([],Just dkMap) diff --git a/ghcide/src/Development/IDE/LSP/Outline.hs b/ghcide/src/Development/IDE/LSP/Outline.hs index af2a0f1c97..2051e3a06e 100644 --- a/ghcide/src/Development/IDE/LSP/Outline.hs +++ b/ghcide/src/Development/IDE/LSP/Outline.hs @@ -29,6 +29,7 @@ import Language.LSP.Protocol.Types (DocumentSymbol (..), TextDocumentIdentifier (TextDocumentIdentifier), type (|?) (InL, InR), uriToFilePath) +import Development.IDE.Core.InputPath (InputPath(InputPath)) moduleOutline @@ -36,7 +37,7 @@ moduleOutline moduleOutline ideState _ DocumentSymbolParams{ _textDocument = TextDocumentIdentifier uri } = liftIO $ case uriToFilePath uri of Just (toNormalizedFilePath' -> fp) -> do - mb_decls <- fmap fst <$> runAction "Outline" ideState (useWithStale GetParsedModule fp) + mb_decls <- fmap fst <$> runAction "Outline" ideState (useWithStale GetParsedModule $ InputPath fp) pure $ case mb_decls of Nothing -> InL [] Just ParsedModule { pm_parsed_source = L _ltop HsModule { hsmodName, hsmodDecls, hsmodImports } } diff --git a/ghcide/src/Development/IDE/Plugin/Test.hs b/ghcide/src/Development/IDE/Plugin/Test.hs index e24bcfeee9..3aa9becde4 100644 --- a/ghcide/src/Development/IDE/Plugin/Test.hs +++ b/ghcide/src/Development/IDE/Plugin/Test.hs @@ -52,6 +52,8 @@ import Language.LSP.Protocol.Types import qualified "list-t" ListT import qualified StmContainers.Map as STM import System.Time.Extra +import Development.IDE.Core.InputPath (InputPath(InputPath), generalizeProjectInput) +import Development.IDE.Graph.Internal.Rules (InputClass(ProjectHaskellFiles)) type Age = Int data TestRequest @@ -98,7 +100,7 @@ testRequestHandler _ (BlockSeconds secs) = do return (Right A.Null) testRequestHandler s (GetInterfaceFilesDir file) = liftIO $ do let nfp = fromUri $ toNormalizedUri file - sess <- runAction "Test - GhcSession" s $ use_ GhcSession nfp + sess <- runAction "Test - GhcSession" s $ use_ GhcSession $ InputPath nfp let hiPath = hiDir $ hsc_dflags $ hscEnv sess return $ Right (toJSON hiPath) testRequestHandler s GetShakeSessionQueueCount = liftIO $ do @@ -111,7 +113,7 @@ testRequestHandler s WaitForShakeQueue = liftIO $ do return $ Right A.Null testRequestHandler s (WaitForIdeRule k file) = liftIO $ do let nfp = fromUri $ toNormalizedUri file - success <- runAction ("WaitForIdeRule " <> k <> " " <> show file) s $ parseAction (fromString k) nfp + success <- runAction ("WaitForIdeRule " <> k <> " " <> show file) s $ parseAction (fromString k) $ InputPath $ nfp let res = WaitForIdeRuleResult <$> success return $ bimap PluginInvalidParams toJSON res testRequestHandler s GetBuildKeysBuilt = liftIO $ do @@ -147,7 +149,7 @@ getDatabaseKeys field db = do step <- shakeGetBuildStep db return [ k | (k, res) <- keys, field res == Step step] -parseAction :: CI String -> NormalizedFilePath -> Action (Either Text Bool) +parseAction :: CI String -> InputPath ProjectHaskellFiles -> Action (Either Text Bool) parseAction "typecheck" fp = Right . isJust <$> use TypeCheck fp parseAction "getLocatedImports" fp = Right . isJust <$> use GetLocatedImports fp parseAction "getmodsummary" fp = Right . isJust <$> use GetModSummary fp @@ -155,8 +157,8 @@ parseAction "getmodsummarywithouttimestamps" fp = Right . isJust <$> use GetModS parseAction "getparsedmodule" fp = Right . isJust <$> use GetParsedModule fp parseAction "ghcsession" fp = Right . isJust <$> use GhcSession fp parseAction "ghcsessiondeps" fp = Right . isJust <$> use GhcSessionDeps fp -parseAction "gethieast" fp = Right . isJust <$> use GetHieAst fp -parseAction "getFileContents" fp = Right . isJust <$> use GetFileContents fp +parseAction "gethieast" fp = Right . isJust <$> use GetHieAst (generalizeProjectInput fp) +parseAction "getFileContents" fp = Right . isJust <$> use GetFileContents (generalizeProjectInput fp) parseAction other _ = return $ Left $ "Cannot parse ide rule: " <> pack (original other) -- | a command that blocks forever. Used for testing diff --git a/ghcide/src/Development/IDE/Spans/AtPoint.hs b/ghcide/src/Development/IDE/Spans/AtPoint.hs index 88c6570b23..07bc144f5f 100644 --- a/ghcide/src/Development/IDE/Spans/AtPoint.hs +++ b/ghcide/src/Development/IDE/Spans/AtPoint.hs @@ -208,11 +208,11 @@ gotoDefinition withHieDb getHieFile ideOpts imports srcSpans pos atPoint :: IdeOptions -> HieAstResult - -> DocAndTyThingMap - -> HscEnv + -> Maybe DocAndTyThingMap + -> Maybe HscEnv -> Position -> IO (Maybe (Maybe Range, [T.Text])) -atPoint IdeOptions{} (HAR _ hf _ _ (kind :: HieKind hietype)) (DKMap dm km) env pos = +atPoint IdeOptions{} (HAR _ hf _ _ (kind :: HieKind hietype)) mDkMap mEnv pos = listToMaybe <$> sequence (pointCommand hf pos hoverInfo) where -- Hover info for values/data @@ -251,9 +251,15 @@ atPoint IdeOptions{} (HAR _ hf _ _ (kind :: HieKind hietype)) (DKMap dm km) env prettyName (Right n, dets) = pure $ T.unlines $ wrapHaskell (printOutputable n <> maybe "" (" :: " <>) ((prettyType <$> identType dets) <|> maybeKind)) : maybeToList (pretty (definedAt n) (prettyPackageName n)) - ++ catMaybes [ T.unlines . spanDocToMarkdown <$> lookupNameEnv dm n + ++ catMaybes [ T.unlines . spanDocToMarkdown <$> maybeDoc ] - where maybeKind = fmap printOutputable $ safeTyThingType =<< lookupNameEnv km n + where maybeKind = do + (DKMap _ km) <- mDkMap + nameEnv <- lookupNameEnv km n + printOutputable <$> safeTyThingType nameEnv + maybeDoc = do + (DKMap dm _) <- mDkMap + lookupNameEnv dm n pretty Nothing Nothing = Nothing pretty (Just define) Nothing = Just $ define <> "\n" pretty Nothing (Just pkgName) = Just $ pkgName <> "\n" @@ -270,7 +276,8 @@ atPoint IdeOptions{} (HAR _ hf _ _ (kind :: HieKind hietype)) (DKMap dm km) env -- the package(with version) this `ModuleName` belongs to. packageNameForImportStatement :: ModuleName -> IO T.Text packageNameForImportStatement mod = do - mpkg <- findImportedModule env mod :: IO (Maybe Module) + mpkg <- fmap join $ sequence $ + flip findImportedModule mod <$> mEnv :: IO (Maybe Module) let moduleName = printOutputable mod case mpkg >>= packageNameWithVersion of Nothing -> pure moduleName @@ -278,14 +285,22 @@ atPoint IdeOptions{} (HAR _ hf _ _ (kind :: HieKind hietype)) (DKMap dm km) env -- Return the package name and version of a module. -- For example, given module `Data.List`, it should return something like `base-4.x`. - packageNameWithVersion :: Module -> Maybe T.Text - packageNameWithVersion m = do - let pid = moduleUnit m - conf <- lookupUnit env pid - let pkgName = T.pack $ unitPackageNameString conf - version = T.pack $ showVersion (unitPackageVersion conf) - pure $ pkgName <> "-" <> version - + packageNameWithVersion m = let pid = moduleUnit m in + case mEnv of + -- If we have an HscEnv (because this is a project file), + -- we can get the package name from that. + Just env -> do + conf <- lookupUnit env pid + let pkgName = T.pack $ unitPackageNameString conf + version = T.pack $ showVersion (unitPackageVersion conf) + pure $ pkgName <> "-" <> version + -- If we don't have an HscEnv (because this is a dependency file), + -- then we can get a similar format for the package name + -- from the UnitId. + Nothing -> + let uid = toUnitId pid + pkgStr = takeWhile (/= ':') $ show uid + in Just $ T.pack pkgStr -- Type info for the current node, it may contains several symbols -- for one range, like wildcard types :: [hietype] From 228045b629ea10e68e7ed91976e0c913f2529995 Mon Sep 17 00:00:00 2001 From: Elodie Lander Date: Wed, 8 Jan 2025 07:56:07 -0500 Subject: [PATCH 14/14] WIP plugin related type errors --- .../session-loader/Development/IDE/Session.hs | 6 ++-- .../src/Development/IDE/Plugin/TypeLenses.hs | 28 +++++++++++++------ ghcide/src/Development/IDE/Spans/Pragmas.hs | 12 ++++---- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/ghcide/session-loader/Development/IDE/Session.hs b/ghcide/session-loader/Development/IDE/Session.hs index a1d778ab0e..23fd84c714 100644 --- a/ghcide/session-loader/Development/IDE/Session.hs +++ b/ghcide/session-loader/Development/IDE/Session.hs @@ -125,6 +125,7 @@ import GHC.Driver.Errors.Types import GHC.Types.Error (errMsgDiagnostic, singleMessage) import GHC.Unit.State +import Development.IDE.Core.InputPath (generalizeProjectInput, classifyProjectHaskellInputs) data Log = LogSettingInitialDynFlags @@ -592,8 +593,9 @@ loadSessionWithOptions recorder SessionLoadingOptions{..} rootDir que = do unless (null new_deps || not checkProject) $ do cfps' <- liftIO $ filterM (IO.doesFileExist . fromNormalizedFilePath) (concatMap targetLocations all_targets) void $ shakeEnqueue extras $ mkDelayedAction "InitialLoad" Debug $ void $ do - mmt <- uses GetModificationTime cfps' - let cs_exist = catMaybes (zipWith (<$) cfps' mmt) + let cfps'' = classifyProjectHaskellInputs cfps' + mmt <- uses GetModificationTime $ generalizeProjectInput <$> cfps'' + let cs_exist = catMaybes (zipWith (<$) cfps'' mmt) modIfaces <- uses GetModIface cs_exist -- update exports map shakeExtras <- getShakeExtras diff --git a/ghcide/src/Development/IDE/Plugin/TypeLenses.hs b/ghcide/src/Development/IDE/Plugin/TypeLenses.hs index 51d25e995b..a94bbfa6b1 100644 --- a/ghcide/src/Development/IDE/Plugin/TypeLenses.hs +++ b/ghcide/src/Development/IDE/Plugin/TypeLenses.hs @@ -24,7 +24,7 @@ import Data.Aeson.Types (toJSON) import qualified Data.Aeson.Types as A import Data.List (find) import qualified Data.Map as Map -import Data.Maybe (catMaybes, maybeToList) +import Data.Maybe (catMaybes, maybeToList, listToMaybe) import qualified Data.Text as T import Development.IDE (GhcSession (..), HscEnvEq (hscEnv), @@ -81,6 +81,9 @@ import Language.LSP.Protocol.Types (ApplyWorkspaceEditParams WorkspaceEdit (WorkspaceEdit), type (|?) (..)) import Text.Regex.TDFA ((=~)) +import Development.IDE.Graph (RuleInput) +import Development.IDE.Graph.Internal.Rules (InputClass(ProjectHaskellFiles)) +import Development.IDE.Core.InputPath (classifyProjectHaskellInputs) data Log = LogShake Shake.Log deriving Show @@ -167,18 +170,24 @@ codeLensProvider ideState pId CodeLensParams{_textDocument = TextDocumentIdentif codeLensResolveProvider :: ResolveFunction IdeState TypeLensesResolve Method_CodeLensResolve codeLensResolveProvider ideState pId lens@CodeLens{_range} uri TypeLensesResolve = do nfp <- getNormalizedFilePathE uri - (gblSigs@(GlobalBindingTypeSigsResult _), pm) <- - runActionE "codeLens.GetGlobalBindingTypeSigs" ideState - $ useWithStaleE GetGlobalBindingTypeSigs nfp + let mInput = listToMaybe $ classifyProjectHaskellInputs [nfp] + (mGblSigs, mPm) <- + case mInput of + Nothing -> pure (Nothing, Nothing) + Just input -> do + (gblSigs@(GlobalBindingTypeSigsResult _), pm) <- + runActionE "codeLens.GetGlobalBindingTypeSigs" ideState + $ useWithStaleE GetGlobalBindingTypeSigs input + pure (Just gblSigs, Just pm) -- regardless of how the original lens was generated, we want to get the range -- that the global bindings rule would expect here, hence the need to reverse -- position map the range, regardless of whether it was position mapped in the -- beginning or freshly taken from diagnostics. - newRange <- handleMaybe PluginStaleResolve (fromCurrentRange pm _range) + newRange <- handleMaybe PluginStaleResolve (mPm >>= flip fromCurrentRange _range) -- We also pass on the PositionMapping so that the generated text edit can -- have the range adjusted. (title, edit) <- - handleMaybe PluginStaleResolve $ suggestGlobalSignature' False (Just gblSigs) (Just pm) newRange + handleMaybe PluginStaleResolve $ suggestGlobalSignature' False mGblSigs mPm newRange pure $ lens & L.command ?~ generateLensCommand pId uri title edit generateLensCommand :: PluginId -> Uri -> T.Text -> TextEdit -> Command @@ -295,13 +304,14 @@ instance NFData GlobalBindingTypeSigsResult where rnf = rwhnf type instance RuleResult GetGlobalBindingTypeSigs = GlobalBindingTypeSigsResult +type instance RuleInput GetGlobalBindingTypeSigs = ProjectHaskellFiles rules :: Recorder (WithPriority Log) -> Rules () rules recorder = do - define (cmapWithPrio LogShake recorder) $ \GetGlobalBindingTypeSigs nfp -> do - tmr <- use TypeCheck nfp + define (cmapWithPrio LogShake recorder) $ \GetGlobalBindingTypeSigs input -> do + tmr <- use TypeCheck input -- we need session here for tidying types - hsc <- use GhcSession nfp + hsc <- use GhcSession input result <- liftIO $ gblBindingType (hscEnv <$> hsc) (tmrTypechecked <$> tmr) pure ([], result) diff --git a/ghcide/src/Development/IDE/Spans/Pragmas.hs b/ghcide/src/Development/IDE/Spans/Pragmas.hs index a2b4981a38..52ff905109 100644 --- a/ghcide/src/Development/IDE/Spans/Pragmas.hs +++ b/ghcide/src/Development/IDE/Spans/Pragmas.hs @@ -15,7 +15,7 @@ import qualified Data.List as List import qualified Data.Maybe as Maybe import Data.Text (Text, pack) import qualified Data.Text as Text -import Development.IDE (srcSpanToRange, IdeState, NormalizedFilePath, GhcSession (..), getFileContents, hscEnv, runAction) +import Development.IDE (srcSpanToRange, IdeState, GhcSession (..), getFileContents, hscEnv, runAction) import Development.IDE.GHC.Compat import Development.IDE.GHC.Compat.Util import qualified Language.LSP.Protocol.Types as LSP @@ -26,6 +26,8 @@ import Ide.Types (PluginId(..)) import qualified Data.Text as T import Development.IDE.Core.PluginUtils import qualified Language.LSP.Protocol.Lens as L +import Development.IDE.Core.InputPath (InputPath, generalizeProjectInput) +import Development.IDE.Graph.Internal.Rules (InputClass(ProjectHaskellFiles)) getNextPragmaInfo :: DynFlags -> Maybe Text -> NextPragmaInfo getNextPragmaInfo dynFlags mbSourceText = @@ -53,10 +55,10 @@ insertNewPragma (NextPragmaInfo nextPragmaLine _) newPragma = LSP.TextEdit prag pragmaInsertPosition = LSP.Position (fromIntegral nextPragmaLine) 0 pragmaInsertRange = LSP.Range pragmaInsertPosition pragmaInsertPosition -getFirstPragma :: MonadIO m => PluginId -> IdeState -> NormalizedFilePath -> ExceptT PluginError m NextPragmaInfo -getFirstPragma (PluginId pId) state nfp = do - (hscEnv -> hsc_dflags -> sessionDynFlags, _) <- runActionE (T.unpack pId <> ".GhcSession") state $ useWithStaleE GhcSession nfp - (_, fileContents) <- liftIO $ runAction (T.unpack pId <> ".GetFileContents") state $ getFileContents nfp +getFirstPragma :: MonadIO m => PluginId -> IdeState -> InputPath ProjectHaskellFiles -> ExceptT PluginError m NextPragmaInfo +getFirstPragma (PluginId pId) state input = do + (hscEnv -> hsc_dflags -> sessionDynFlags, _) <- runActionE (T.unpack pId <> ".GhcSession") state $ useWithStaleE GhcSession input + (_, fileContents) <- liftIO $ runAction (T.unpack pId <> ".GetFileContents") state $ getFileContents $ generalizeProjectInput input pure $ getNextPragmaInfo sessionDynFlags fileContents -- Pre-declaration comments parser -----------------------------------------------------