content: {-# LANGUAGE LambdaCase      #-}
  {-# LANGUAGE RecordWildCards #-}
  {-# LANGUAGE DeriveTraversable #-}
  
  -- | A module graph should be constructed once and never change from there onwards.
  --
  -- The only operations should be for building the 'ModuleGraph'
  -- (once and for all -- no update-like/insert-like functions)
  -- and querying the structure in various ways, e.g. to determine reachability.
  --
  -- We should avoid exposing fields like 'mg_mss' since it may be a footgun
  -- trying to use the nodes directly... We do still expose it, but it feels like
  -- all its use cases would be better served by a more proper ModuleGraph
  -- abstraction
  module GHC.Unit.Module.Graph
     (
      -- * Construct a module graph
      --
      -- | A module graph should be constructed once by downsweep and never modified.
       ModuleGraph(..)
     , emptyMG
     , mkModuleGraph
     , mkModuleGraphChecked
  
     -- * Invariant checking
     , checkModuleGraph
     , ModuleGraphInvariantError(..)
  
      -- * Nodes in a module graph
      --
      -- | The user-facing nodes in a module graph are 'ModuleGraphNode's.
      -- There are a few things which we can query out of each 'ModuleGraphNode':
      --
      -- - 'mgNodeDependencies' gets the immediate dependencies of this node
      -- - 'mgNodeUnitId' returns the 'UnitId' of that node
      -- - 'mgNodeModSum' extracts the 'ModSummary' of a node if exists
     , ModuleGraphNode(..)
     , mgNodeDependencies
     , mgNodeIsModule
     , mgNodeUnitId
  
     , ModuleNodeEdge(..)
     , mkModuleEdge
     , mkNormalEdge
  
     , ModuleNodeInfo(..)
     , moduleNodeInfoModule
     , moduleNodeInfoUnitId
     , moduleNodeInfoMnwib
     , moduleNodeInfoModuleName
     , moduleNodeInfoModNodeKeyWithUid
     , moduleNodeInfoHscSource
     , moduleNodeInfoLocation
     , isBootModuleNodeInfo
      -- * Module graph operations
     , lengthMG
     , isEmptyMG
      -- ** 'ModSummary' operations
      --
      -- | A couple of operations on the module graph allow access to the
      -- 'ModSummary's of the modules in it contained.
      --
      -- In particular, 'mapMG' and 'mapMGM' allow updating these 'ModSummary's
      -- (without changing the 'ModuleGraph' structure itself!).
      -- 'mgModSummaries' lists out all 'ModSummary's, and
      -- 'mgLookupModule' looks up a 'ModSummary' for a given module.
     , mapMG, mgMapM
     , mgModSummaries
     , mgLookupModule
     , mgLookupModuleName
     , mgHasHoles
     , showModMsg
  
      -- ** Reachability queries
      --
      -- | A module graph explains the structure and relationship between the
      -- modules being compiled. Often times, this structure is relevant to
      -- answer reachability queries -- is X reachable from Y; or, what is the
      -- transitive closure of Z?
     , mgReachable
     , mgReachableLoop
     , mgQuery
     , ZeroScopeKey(..)
     , mgQueryZero
     , mgQueryMany
     , mgQueryManyZero
     , mgMember
  
      -- ** Other operations
      --
      -- | These operations allow more-internal-than-ideal access to the
      -- ModuleGraph structure. Ideally, we could restructure the code using
      -- these functions to avoid deconstructing/reconstructing the ModuleGraph
      -- and instead extend the "proper interface" of the ModuleGraph to achieve
      -- what is currently done but through a better abstraction.
     , mgModSummaries'
     , moduleGraphNodes
     , moduleGraphModulesBelow -- needed for 'hptSomeThingsBelowUs',
                               -- but I think we could be more clever and cache
                               -- the graph-ixs of boot modules to efficiently
                               -- filter them out of the returned list.
                               -- hptInstancesBelow is re-doing that work every
                               -- time it's called.
     , filterToposortToModules
     , moduleGraphNodesZero
     , StageSummaryNode
     , stageSummaryNodeSummary
     , stageSummaryNodeKey
     , mkStageDeps
  
      -- * Keys into the 'ModuleGraph'
     , NodeKey(..)
     , mkNodeKey
     , nodeKeyUnitId
     , nodeKeyModName
     , ModNodeKey
     , ModNodeKeyWithUid(..)
     , mnkToModule
     , moduleToMnk
     , mnkToInstalledModule
     , installedModuleToMnk
     , mnkIsBoot
     , msKey
     , mnKey
     , miKey
  
     , ImportLevel(..)
  
      -- ** Internal node representation
      --
      -- | 'SummaryNode' is the internal representation for each node stored in
      -- the graph. It's not immediately clear to me why users do depend on them.
     , SummaryNode
     , summaryNodeSummary
     , summaryNodeKey
  
     )
  where
  
  import GHC.Prelude
  import GHC.Platform
  
  import GHC.Data.Maybe
  import Data.Either
  import GHC.Data.Graph.Directed
  import GHC.Data.Graph.Directed.Reachability
  
  import GHC.Driver.Backend
  import GHC.Driver.DynFlags
  
  import GHC.Types.SourceFile ( hscSourceString, isHsigFile, HscSource(..))
  import GHC.Types.Basic
  
  import GHC.Unit.Module.ModSummary
  import GHC.Unit.Types
  import GHC.Utils.Outputable
  import GHC.Unit.Module.ModIface
  import GHC.Utils.Misc ( partitionWith )
  
  import System.FilePath
  import qualified Data.Map as Map
  import GHC.Types.Unique.DSet
  import qualified Data.Set as Set
  import Data.Set (Set)
  import GHC.Unit.Module
  import GHC.Unit.Module.ModNodeKey
  import GHC.Unit.Module.Stage
  import GHC.Linker.Static.Utils
  
  import Data.Bifunctor
  import Data.Function
  import Data.List (sort)
  import Control.Monad
  import qualified GHC.LanguageExtensions as LangExt
  
  -- | A '@ModuleGraph@' contains all the nodes from the home package (only). See
  -- '@ModuleGraphNode@' for information about the nodes.
  --
  -- Modules need to be compiled. hs-boots need to be typechecked before
  -- the associated "real" module so modules with {-# SOURCE #-} imports can be
  -- built. Instantiations also need to be typechecked to ensure that the module
  -- fits the signature. Substantiation typechecking is roughly comparable to the
  -- check that the module and its hs-boot agree.
  --
  -- The graph is not necessarily stored in topologically-sorted order. Use
  -- 'GHC.topSortModuleGraph' and 'GHC.Data.Graph.Directed.flattenSCC' to achieve this.
  data ModuleGraph = ModuleGraph
    { mg_mss :: [ModuleGraphNode]
    , mg_graph :: (ReachabilityIndex SummaryNode, NodeKey -> Maybe SummaryNode)
    , mg_loop_graph :: (ReachabilityIndex SummaryNode, NodeKey -> Maybe SummaryNode)
    , mg_zero_graph :: (ReachabilityIndex ZeroSummaryNode, ZeroScopeKey -> Maybe ZeroSummaryNode)
  
      -- `mg_graph` and `mg_loop_graph` cached transitive dependency calculations
      -- so that a lot of work is not repeated whenever the transitive
      -- dependencies need to be calculated (for example, hptInstances).
      --
      --- - `mg_graph` is a reachability index constructed from a module
      -- graph /with/ boot nodes (which make the graph acyclic), and
      --
      --- * `mg_loop_graph` is a reachability index for the graph /without/
      -- hs-boot nodes, that may be cyclic.
  
    , mg_has_holes :: !Bool
    -- Cached computation, whether any of the ModuleGraphNode are isHoleModule,
    -- This is only used for a hack in GHC.Iface.Load to do with backpack, please
    -- remove this at the earliest opportunity.
    }
  
  -- | Why do we ever need to construct empty graphs? Is it because of one shot mode?
  emptyMG :: ModuleGraph
  emptyMG = ModuleGraph [] (graphReachability emptyGraph, const Nothing)
                           (graphReachability emptyGraph, const Nothing)
                           (graphReachability emptyGraph, const Nothing)
                           False
  
  -- | Construct a module graph. This function should be the only entry point for
  -- building a 'ModuleGraph', since it is supposed to be built once and never modified.
  --
  -- If you ever find the need to build a 'ModuleGraph' iteratively, don't
  -- add insert and update functions to the API since they become footguns.
  -- Instead, design an API that allows iterative construction without posterior
  -- modification, perhaps like what is done for building arrays from mutable
  -- arrays.
  mkModuleGraph :: [ModuleGraphNode] -> ModuleGraph
  mkModuleGraph = foldr (flip extendMG) emptyMG
  
  -- | A version of mkModuleGraph that checks the module graph for invariants.
  mkModuleGraphChecked :: [ModuleGraphNode] -> Either [ModuleGraphInvariantError] ModuleGraph
  mkModuleGraphChecked nodes =
    let mg = mkModuleGraph nodes
    in case checkModuleGraph mg of
         [] -> Right mg
         errors -> Left errors
  
  --------------------------------------------------------------------------------
  -- * Module Graph Nodes
  --------------------------------------------------------------------------------
  
  -- | A '@ModuleGraphNode@' is a node in the '@ModuleGraph@'.
  -- Edges between nodes mark dependencies arising from module imports
  -- and dependencies arising from backpack instantiations.
  data ModuleGraphNode
    -- | Instantiation nodes track the instantiation of other units
    -- (backpack dependencies) with the holes (signatures) of the current package.
    = InstantiationNode UnitId InstantiatedUnit
    -- | There is a module node for each module being built.
    -- A node is either fixed or can be compiled.
    -- - Fixed modules are not compiled, the artifacts are just loaded from disk.
    --   It is up to your to make sure the artifacts are up to date and available.
    -- - Compile modules are compiled from source if needed.
    | ModuleNode [ModuleNodeEdge] ModuleNodeInfo
    -- | Link nodes are whether are are creating a linked product (ie executable/shared object etc) for a unit.
    | LinkNode [NodeKey] UnitId
    -- | Package dependency
    | UnitNode [UnitId] UnitId
  
  
  data ModuleNodeEdge = ModuleNodeEdge { edgeLevel :: ImportLevel
                                       , edgeTargetKey :: NodeKey }
  
  mkModuleEdge :: ImportLevel -> NodeKey -> ModuleNodeEdge
  mkModuleEdge level key = ModuleNodeEdge level key
  
  -- | A 'normal' edge in the graph which isn't offset by an import stage.
  mkNormalEdge :: NodeKey -> ModuleNodeEdge
  mkNormalEdge = mkModuleEdge NormalLevel
  
  instance Outputable ModuleNodeEdge where
    ppr (ModuleNodeEdge level key) =
      let level_str = case level of
                        NormalLevel -> ""
                        SpliceLevel -> "(S)"
                        QuoteLevel -> "(Q)"
      in text level_str <> ppr key
  
  data ModuleGraphInvariantError =
          FixedNodeDependsOnCompileNode ModNodeKeyWithUid [NodeKey]
        | DuplicateModuleNodeKey NodeKey
        | DependencyNotInGraph NodeKey [NodeKey]
        deriving (Eq, Ord)
  
  instance Outputable ModuleGraphInvariantError where
    ppr = \case
      FixedNodeDependsOnCompileNode key bad_deps ->
        text "Fixed node" <+> ppr key <+> text "depends on compile nodes" <+> ppr bad_deps
      DuplicateModuleNodeKey k ->
        text "Duplicate module node key" <+> ppr k
      DependencyNotInGraph from to ->
        text "Dependency not in graph" <+> ppr from <+> text "->" <+> ppr to
  
  -- Used for invariant checking. Is a NodeKey fixed or compilable?
  data ModuleNodeType = MN_Fixed | MN_Compile
  
  instance Outputable ModuleNodeType where
    ppr = \case
      MN_Fixed -> text "Fixed"
      MN_Compile -> text "Compile"
  
  moduleNodeType :: ModuleGraphNode -> ModuleNodeType
  moduleNodeType (ModuleNode _ (ModuleNodeCompile _)) = MN_Compile
  moduleNodeType (ModuleNode _ (ModuleNodeFixed _ _)) = MN_Fixed
  moduleNodeType (UnitNode {}) = MN_Fixed
  moduleNodeType _ = MN_Compile
  
  checkModuleGraph :: ModuleGraph -> [ModuleGraphInvariantError]
  checkModuleGraph ModuleGraph{..} =
    mapMaybe (checkFixedModuleInvariant node_types) mg_mss
    ++ mapMaybe (checkAllDependenciesInGraph node_types) mg_mss
    ++ duplicate_errs
    where
      duplicate_errs = rights (Map.elems node_types)
  
      node_types :: Map.Map NodeKey (Either ModuleNodeType ModuleGraphInvariantError)
      node_types = Map.fromListWithKey go [ (mkNodeKey n, Left (moduleNodeType n)) | n <- mg_mss ]
        where
          -- Multiple nodes with the same key are not allowed.
          go :: NodeKey -> Either ModuleNodeType ModuleGraphInvariantError
                        -> Either ModuleNodeType ModuleGraphInvariantError
                        -> Either ModuleNodeType ModuleGraphInvariantError
          go k _ _ = Right (DuplicateModuleNodeKey k)
  
  -- | Check that all dependencies in the graph are present in the node_types map.
  -- This is a helper function used by checkModuleGraph.
  checkAllDependenciesInGraph :: Map.Map NodeKey (Either ModuleNodeType ModuleGraphInvariantError)
                              -> ModuleGraphNode
                              -> Maybe ModuleGraphInvariantError
  checkAllDependenciesInGraph node_types node =
    let nodeKey = mkNodeKey node
        deps = mgNodeDependencies False node
        missingDeps = filter (\dep -> not (Map.member dep node_types)) deps
    in if null missingDeps
       then Nothing
       else Just (DependencyNotInGraph nodeKey missingDeps)
  
  
  -- | Check if for the fixed module node invariant:
  --
  --   Fixed nodes can only depend on other fixed nodes.
  checkFixedModuleInvariant :: Map.Map NodeKey (Either ModuleNodeType ModuleGraphInvariantError)
                  -> ModuleGraphNode
                  -> Maybe ModuleGraphInvariantError
  checkFixedModuleInvariant node_types node = case node of
    ModuleNode deps (ModuleNodeFixed key _) ->
      let check_node dep = case Map.lookup dep node_types of
                             -- Dependency is not fixed
                             Just (Left MN_Compile) -> Just dep
                             _ -> Nothing
          bad_deps = mapMaybe check_node (map edgeTargetKey deps)
      in if null bad_deps
         then Nothing
         else Just (FixedNodeDependsOnCompileNode key bad_deps)
  
    _ -> Nothing
  
  
  {- Note [Module Types in the ModuleGraph]
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  
  Modules can be one of two different types in the module graph.
  
  1. ModuleNodeCompile, modules with source files we can compile.
  2. ModuleNodeFixed, modules which we presume are already compiled and available.
  
  The ModuleGraph can contain a combination of these two types of nodes but must
  obey the invariant that Fixed nodes only depend on other Fixed nodes. This invariant
  can be checked by the `checkModuleGraph` function, but it's
  the responsibility of the code constructing the ModuleGraph to ensure it is upheld.
  
  At the moment, when using --make mode, GHC itself will only use `ModuleNodeCompile` nodes.
  
  In oneshot mode, we don't have access to the source files of dependencies but sometimes need to know
  information about the module graph still (for example, getLinkDeps).
  
  In theory, the whole compiler will work if an API program uses ModuleNodeFixed nodes, and
  there is a simple test in FixedNodes, which can be extended in future to cover
  any missing cases.
  
  -}
  data ModuleNodeInfo = ModuleNodeFixed ModNodeKeyWithUid ModLocation
                      | ModuleNodeCompile ModSummary
  
  -- | Extract the Module from a ModuleNodeInfo
  moduleNodeInfoModule :: ModuleNodeInfo -> Module
  moduleNodeInfoModule (ModuleNodeFixed key _) = mnkToModule key
  moduleNodeInfoModule (ModuleNodeCompile ms) = ms_mod ms
  
  -- | Extract the ModNodeKeyWithUid from a ModuleNodeInfo
  moduleNodeInfoModNodeKeyWithUid :: ModuleNodeInfo -> ModNodeKeyWithUid
  moduleNodeInfoModNodeKeyWithUid (ModuleNodeFixed key _) = key
  moduleNodeInfoModNodeKeyWithUid (ModuleNodeCompile ms) = msKey ms
  
  -- | Extract the HscSource from a ModuleNodeInfo, if we can determine it.
  moduleNodeInfoHscSource :: ModuleNodeInfo -> Maybe HscSource
  moduleNodeInfoHscSource (ModuleNodeFixed _ _) = Nothing
  moduleNodeInfoHscSource (ModuleNodeCompile ms) = Just (ms_hsc_src ms)
  
  -- | Extract the ModLocation from a ModuleNodeInfo
  moduleNodeInfoLocation :: ModuleNodeInfo -> ModLocation
  moduleNodeInfoLocation (ModuleNodeFixed _ loc) = loc
  moduleNodeInfoLocation (ModuleNodeCompile ms) = ms_location ms
  
  -- | Extract the IsBootInterface from a ModuleNodeInfo
  isBootModuleNodeInfo :: ModuleNodeInfo -> IsBootInterface
  isBootModuleNodeInfo (ModuleNodeFixed mnwib _) = mnkIsBoot mnwib
  isBootModuleNodeInfo (ModuleNodeCompile ms) = isBootSummary ms
  
  -- | Extract the ModuleName from a ModuleNodeInfo
  moduleNodeInfoModuleName :: ModuleNodeInfo -> ModuleName
  moduleNodeInfoModuleName m = moduleName (moduleNodeInfoModule m)
  
  moduleNodeInfoUnitId :: ModuleNodeInfo -> UnitId
  moduleNodeInfoUnitId (ModuleNodeFixed key _) = mnkUnitId key
  moduleNodeInfoUnitId (ModuleNodeCompile ms) = ms_unitid ms
  
  moduleNodeInfoMnwib :: ModuleNodeInfo -> ModuleNameWithIsBoot
  moduleNodeInfoMnwib (ModuleNodeFixed key _) = mnkModuleName key
  moduleNodeInfoMnwib (ModuleNodeCompile ms) = ms_mnwib ms
  
  -- | Collect the immediate dependencies of a ModuleGraphNode,
  -- optionally avoiding hs-boot dependencies.
  -- If the drop_hs_boot_nodes flag is False, and if this is a .hs and there is
  -- an equivalent .hs-boot, add a link from the former to the latter.  This
  -- has the effect of detecting bogus cases where the .hs-boot depends on the
  -- .hs, by introducing a cycle.  Additionally, it ensures that we will always
  -- process the .hs-boot before the .hs, and so the HomePackageTable will always
  -- have the most up to date information.
  mgNodeDependencies :: Bool -> ModuleGraphNode -> [NodeKey]
  mgNodeDependencies drop_hs_boot_nodes = \case
      LinkNode deps _uid -> deps
      InstantiationNode uid iuid ->
        [ NodeKey_Module (ModNodeKeyWithUid (GWIB mod NotBoot) uid) | mod <- uniqDSetToList (instUnitHoles iuid) ]
        ++ [ NodeKey_ExternalUnit (instUnitInstanceOf iuid) ]
      ModuleNode deps _ms ->
        map (drop_hs_boot . edgeTargetKey) deps
      UnitNode deps _ -> map NodeKey_ExternalUnit deps
    where
      -- Drop hs-boot nodes by using HsSrcFile as the key
      hs_boot_key | drop_hs_boot_nodes = NotBoot -- is regular mod or signature
                  | otherwise          = IsBoot
  
      drop_hs_boot (NodeKey_Module (ModNodeKeyWithUid (GWIB mn IsBoot) uid)) = (NodeKey_Module (ModNodeKeyWithUid (GWIB mn hs_boot_key) uid))
      drop_hs_boot x = x
  
  mgNodeIsModule :: ModuleGraphNode -> Maybe ModuleNodeInfo
  mgNodeIsModule (InstantiationNode {}) = Nothing
  mgNodeIsModule (LinkNode {})          = Nothing
  mgNodeIsModule (ModuleNode _ ms)      = Just ms
  mgNodeIsModule (UnitNode {})       = Nothing
  
  mgNodeUnitId :: ModuleGraphNode -> UnitId
  mgNodeUnitId mgn =
    case mgn of
      InstantiationNode uid _iud -> uid
      ModuleNode _ ms           -> toUnitId (moduleUnit (moduleNodeInfoModule ms))
      LinkNode _ uid             -> uid
      UnitNode _ uid          -> uid
  
  instance Outputable ModuleGraphNode where
    ppr = \case
      InstantiationNode _ iuid -> ppr iuid
      ModuleNode nks ms -> ppr (mnKey ms) <+> ppr nks
      LinkNode uid _     -> text "LN:" <+> ppr uid
      UnitNode _ uid  -> text "P:" <+> ppr uid
  
  instance Eq ModuleGraphNode where
    (==) = (==) `on` mkNodeKey
  
  instance Ord ModuleGraphNode where
    compare = compare `on` mkNodeKey
  
  --------------------------------------------------------------------------------
  -- * Module Graph operations
  --------------------------------------------------------------------------------
  -- | Returns the number of nodes in a 'ModuleGraph'
  lengthMG :: ModuleGraph -> Int
  lengthMG = length . mg_mss
  
  isEmptyMG :: ModuleGraph -> Bool
  isEmptyMG = null . mg_mss
  
  --------------------------------------------------------------------------------
  -- ** ModSummaries
  --------------------------------------------------------------------------------
  
  -- | Map a function 'f' over all the 'ModSummaries'.
  -- To preserve invariants, 'f' can't change the isBoot status.
  mapMG :: (ModSummary -> ModSummary) -> ModuleGraph -> ModuleGraph
  mapMG f mg@ModuleGraph{..} = mg
    { mg_mss = flip fmap mg_mss $ \case
        InstantiationNode uid iuid -> InstantiationNode uid iuid
        LinkNode uid nks -> LinkNode uid nks
        ModuleNode deps (ModuleNodeFixed key loc)  -> ModuleNode deps (ModuleNodeFixed key loc)
        ModuleNode deps (ModuleNodeCompile ms) -> ModuleNode deps (ModuleNodeCompile (f ms))
        UnitNode deps uid -> UnitNode deps uid
    }
  
  -- | Map a function 'f' over all the 'ModSummaries', in 'IO'.
  -- To preserve invariants, 'f' can't change the isBoot status.
  mgMapM :: (ModuleNodeInfo -> IO ModuleNodeInfo) -> ModuleGraph -> IO ModuleGraph
  mgMapM f mg@ModuleGraph{..} = do
    mss' <- forM mg_mss $ \case
      InstantiationNode uid iuid -> pure $ InstantiationNode uid iuid
      LinkNode uid nks -> pure $ LinkNode uid nks
      ModuleNode deps ms  -> ModuleNode deps <$> (f ms)
      UnitNode deps uid -> pure $ UnitNode deps uid
    return $ mg { mg_mss = mss' }
  
  
  mgModSummaries :: ModuleGraph -> [ModSummary]
  mgModSummaries mg = [ m | ModuleNode _ (ModuleNodeCompile m) <- mgModSummaries' mg ]
  
  -- | Look up a non-boot ModSummary in the ModuleGraph.
  --
  -- Careful: Linear in the size of the module graph
  -- MP: This should probably be level aware
  mgLookupModule :: ModuleGraph -> Module -> Maybe ModuleNodeInfo
  mgLookupModule ModuleGraph{..} m = listToMaybe $ mapMaybe go mg_mss
    where
      go (ModuleNode _ ms)
        | NotBoot <- isBootModuleNodeInfo ms
        , moduleNodeInfoModule ms == m
        = Just ms
      go _ = Nothing
  
  -- | Lookup up a 'ModuleNameWithIsBoot' in the 'ModuleGraph'.
  --
  -- Multiple nodes in the 'ModuleGraph' can have the same 'ModuleName'
  -- and 'IsBootInterface'.
  --
  -- Careful: Linear in the size of the module graph.
  mgLookupModuleName :: ModuleGraph -> ModuleNameWithIsBoot -> [ModuleNodeInfo]
  mgLookupModuleName ModuleGraph{..} m = mapMaybe go mg_mss
    where
      go (ModuleNode _ ms)
        | moduleNodeInfoMnwib ms == m
        = Just ms
      go _ = Nothing
  
  mgMember :: ModuleGraph -> NodeKey -> Bool
  mgMember graph k = isJust $ snd (mg_graph graph) k
  
  -- | A function you should not need to use, or desire to use. Only used
  -- in one place, `GHC.Iface.Load` to facilitate a misimplementation in Backpack.
  mgHasHoles :: ModuleGraph -> Bool
  mgHasHoles ModuleGraph{..} = mg_has_holes
  
  --------------------------------------------------------------------------------
  -- ** Reachability
  --------------------------------------------------------------------------------
  
  -- | Return all nodes reachable from the given 'NodeKey'.
  --
  -- @Nothing@ if the key couldn't be found in the graph.
  mgReachable :: ModuleGraph -> NodeKey -> Maybe [ModuleGraphNode]
  mgReachable mg nk = map summaryNodeSummary <$> modules_below where
    (td_map, lookup_node) = mg_graph mg
    modules_below =
      allReachable td_map <$> lookup_node nk
  
  -- | Things which are reachable if hs-boot files are ignored. Used by 'getLinkDeps'
  mgReachableLoop :: ModuleGraph -> [NodeKey] -> [ModuleGraphNode]
  mgReachableLoop mg nk = map summaryNodeSummary modules_below where
    (td_map, lookup_node) = mg_loop_graph mg
    modules_below =
      allReachableMany td_map (mapMaybe lookup_node nk)
  
  
  -- | @'mgQueryZero' g root b@ answers the question: can we reach @b@ from @root@
  -- in the module graph @g@, only using normal (level 0) imports?
  mgQueryZero :: ModuleGraph
              -> ZeroScopeKey
              -> ZeroScopeKey
              -> Bool
  mgQueryZero mg nka nkb = isReachable td_map na nb where
    (td_map, lookup_node) = mg_zero_graph mg
    na = expectJust $ lookup_node nka
    nb = expectJust $ lookup_node nkb
  
  
  -- | Reachability Query.
  --
  -- @mgQuery(g, a, b)@ asks:
  -- Can we reach @b@ from @a@ in graph @g@?
  --
  -- Both @a@ and @b@ must be in @g@.
  mgQuery :: ModuleGraph -- ^ @g@
          -> NodeKey -- ^ @a@
          -> NodeKey -- ^ @b@
          -> Bool -- ^ @b@ is reachable from @a@
  mgQuery mg nka nkb = isReachable td_map na nb where
    (td_map, lookup_node) = mg_graph mg
    na = expectJust $ lookup_node nka
    nb = expectJust $ lookup_node nkb
  
  -- | Many roots reachability Query.
  --
  -- @mgQuery(g, roots, b)@ asks:
  -- Can we reach @b@ from any of the @roots@ in graph @g@?
  --
  -- Node @b@ must be in @g@.
  mgQueryMany :: ModuleGraph -- ^ @g@
              -> [NodeKey] -- ^ @roots@
              -> NodeKey -- ^ @b@
              -> Bool -- ^ @b@ is reachable from @roots@
  mgQueryMany mg roots nkb = isReachableMany td_map nroots nb where
    (td_map, lookup_node) = mg_graph mg
    nroots = mapMaybe lookup_node roots
    nb = expectJust $ lookup_node nkb
  
  -- | Many roots reachability Query.
  --
  -- @mgQuery(g, roots, b)@ asks:
  -- Can we reach @b@ from any of the @roots@ in graph @g@, only using normal (level 0) imports?
  --
  -- Node @b@ must be in @g@.
  mgQueryManyZero :: ModuleGraph -- ^ @g@
              -> [ZeroScopeKey] -- ^ @roots@
              -> ZeroScopeKey -- ^ @b@
              -> Bool -- ^ @b@ is reachable from @roots@
  mgQueryManyZero mg roots nkb = isReachableMany td_map nroots nb where
    (td_map, lookup_node) = mg_zero_graph mg
    nroots = mapMaybe lookup_node roots
    nb = expectJust $ lookup_node (pprTrace "mg" (ppr nkb) nkb)
  
  --------------------------------------------------------------------------------
  -- ** Other operations (read haddocks on export list)
  --------------------------------------------------------------------------------
  
  mgModSummaries' :: ModuleGraph -> [ModuleGraphNode]
  mgModSummaries' = mg_mss
  
  -- | Turn a list of graph nodes into an efficient queriable graph.
  -- The first boolean parameter indicates whether nodes corresponding to hs-boot files
  -- should be collapsed into their relevant hs nodes.
  moduleGraphNodes :: Bool
    -> [ModuleGraphNode]
    -> (Graph SummaryNode, NodeKey -> Maybe SummaryNode)
  moduleGraphNodes drop_hs_boot_nodes summaries =
    (graphFromEdgedVerticesUniq nodes, lookup_node)
    where
      -- Map from module to extra boot summary dependencies which need to be merged in
      (boot_summaries, nodes) = bimap Map.fromList id $ partitionWith go numbered_summaries
  
        where
          go (s, key) =
            case s of
                  ModuleNode __deps ms | isBootModuleNodeInfo ms == IsBoot, drop_hs_boot_nodes
                    -- Using nodeDependencies here converts dependencies on other
                    -- boot files to dependencies on dependencies on non-boot files.
                    -> Left (moduleNodeInfoModule ms, mgNodeDependencies drop_hs_boot_nodes s)
                  _ -> normal_case
            where
             normal_case =
                let lkup_key = moduleNodeInfoModule <$> mgNodeIsModule s
                    extra = (lkup_key >>= \key -> Map.lookup key boot_summaries)
  
                in Right $ DigraphNode s key $ out_edge_keys $
                        (fromMaybe [] extra
                          ++ mgNodeDependencies drop_hs_boot_nodes s)
  
      numbered_summaries = zip summaries [1..]
  
      lookup_node :: NodeKey -> Maybe SummaryNode
      lookup_node key = Map.lookup key (unNodeMap node_map)
  
      lookup_key :: NodeKey -> Maybe Int
      lookup_key = fmap summaryNodeKey . lookup_node
  
      node_map :: NodeMap SummaryNode
      node_map = NodeMap $
        Map.fromList [ (mkNodeKey s, node)
                     | node <- nodes
                     , let s = summaryNodeSummary node
                     ]
  
      out_edge_keys :: [NodeKey] -> [Int]
      out_edge_keys = mapMaybe lookup_key
          -- If we want keep_hi_boot_nodes, then we do lookup_key with
          -- IsBoot; else False
  
  
  -- | This function returns all the modules belonging to the home-unit that can
  -- be reached by following the given dependencies. Additionally, if both the
  -- boot module and the non-boot module can be reached, it only returns the
  -- non-boot one.
  moduleGraphModulesBelow :: ModuleGraph -> UnitId -> ModuleNameWithIsBoot -> Set ModNodeKeyWithUid
  moduleGraphModulesBelow mg uid mn = filtered_mods [ mn |  NodeKey_Module mn <- modules_below]
    where
      modules_below = maybe [] (map mkNodeKey) (mgReachable mg (NodeKey_Module (ModNodeKeyWithUid mn uid)))
      filtered_mods = Set.fromDistinctAscList . filter_mods . sort
  
      -- IsBoot and NotBoot modules are necessarily consecutive in the sorted list
      -- (cf Ord instance of GenWithIsBoot). Hence we only have to perform a
      -- linear sweep with a window of size 2 to remove boot modules for which we
      -- have the corresponding non-boot.
      filter_mods = \case
        (r1@(ModNodeKeyWithUid (GWIB m1 b1) uid1) : r2@(ModNodeKeyWithUid (GWIB m2 _) uid2): rs)
          | m1 == m2  && uid1 == uid2 ->
                         let !r' = case b1 of
                                    NotBoot -> r1
                                    IsBoot  -> r2
                         in r' : filter_mods rs
          | otherwise -> r1 : filter_mods (r2:rs)
        rs -> rs
  
  -- | This function filters out all the instantiation nodes from each SCC of a
  -- topological sort. Use this with care, as the resulting "strongly connected components"
  -- may not really be strongly connected in a direct way, as instantiations have been
  -- removed. It would probably be best to eliminate uses of this function where possible.
  filterToposortToModules
    :: [SCC ModuleGraphNode] -> [SCC ModuleNodeInfo]
  filterToposortToModules = mapMaybe $ mapMaybeSCC $ \case
    ModuleNode _deps node -> Just node
    _ -> Nothing
    where
      -- This higher order function is somewhat bogus,
      -- as the definition of "strongly connected component"
      -- is not necessarily respected.
      mapMaybeSCC :: (a -> Maybe b) -> SCC a -> Maybe (SCC b)
      mapMaybeSCC f = \case
        AcyclicSCC a -> AcyclicSCC <$> f a
        CyclicSCC as -> case mapMaybe f as of
          [] -> Nothing
          [a] -> Just $ AcyclicSCC a
          as -> Just $ CyclicSCC as
  
  --------------------------------------------------------------------------------
  -- * Keys into ModuleGraph
  --------------------------------------------------------------------------------
  
  data NodeKey = NodeKey_Unit {-# UNPACK #-} !InstantiatedUnit
               | NodeKey_Module {-# UNPACK #-} !ModNodeKeyWithUid
               | NodeKey_Link !UnitId
               | NodeKey_ExternalUnit !UnitId
    deriving (Eq, Ord)
  
  instance Outputable NodeKey where
    ppr (NodeKey_Unit iu)   = ppr iu
    ppr (NodeKey_Module mk) = ppr mk
    ppr (NodeKey_Link uid)  = ppr uid
    ppr (NodeKey_ExternalUnit uid) = ppr uid
  
  mkNodeKey :: ModuleGraphNode -> NodeKey
  mkNodeKey = \case
    InstantiationNode _ iu -> NodeKey_Unit iu
    ModuleNode _ x -> NodeKey_Module $ mnKey x
    LinkNode _ uid   -> NodeKey_Link uid
    UnitNode _ uid -> NodeKey_ExternalUnit uid
  
  nodeKeyUnitId :: NodeKey -> UnitId
  nodeKeyUnitId (NodeKey_Unit iu)   = instUnitInstanceOf iu
  nodeKeyUnitId (NodeKey_Module mk) = mnkUnitId mk
  nodeKeyUnitId (NodeKey_Link uid)  = uid
  nodeKeyUnitId (NodeKey_ExternalUnit uid) = uid
  
  nodeKeyModName :: NodeKey -> Maybe ModuleName
  nodeKeyModName (NodeKey_Module mk) = Just (gwib_mod $ mnkModuleName mk)
  nodeKeyModName _ = Nothing
  
  msKey :: ModSummary -> ModNodeKeyWithUid
  msKey ms = ModNodeKeyWithUid (ms_mnwib ms) (ms_unitid ms)
  
  mnKey :: ModuleNodeInfo -> ModNodeKeyWithUid
  mnKey (ModuleNodeFixed key _) = key
  mnKey (ModuleNodeCompile ms) = msKey ms
  
  miKey :: ModIface -> ModNodeKeyWithUid
  miKey hmi = ModNodeKeyWithUid (mi_mnwib hmi) ((toUnitId $ moduleUnit (mi_module hmi)))
  
  type ModNodeKey = ModuleNameWithIsBoot
  
  --------------------------------------------------------------------------------
  -- ** Internal node representation (exposed)
  --------------------------------------------------------------------------------
  
  type SummaryNode = Node Int ModuleGraphNode
  
  summaryNodeKey :: SummaryNode -> Int
  summaryNodeKey = node_key
  
  summaryNodeSummary :: SummaryNode -> ModuleGraphNode
  summaryNodeSummary = node_payload
  
  --------------------------------------------------------------------------------
  -- * Misc utilities
  --------------------------------------------------------------------------------
  
  showModMsg :: DynFlags -> ModuleGraphNode -> SDoc
  showModMsg dflags (LinkNode {}) =
        let staticLink = case ghcLink dflags of
                            LinkStaticLib -> True
                            _ -> False
  
            platform  = targetPlatform dflags
            arch_os   = platformArchOS platform
            exe_file  = exeFileName arch_os staticLink (outputFile_ dflags)
        in text exe_file
  showModMsg _ (UnitNode _deps uid) = ppr uid
  showModMsg _ (InstantiationNode _uid indef_unit) =
    ppr $ instUnitInstanceOf indef_unit
  showModMsg dflags (ModuleNode _ mni) =
    if gopt Opt_HideSourcePaths dflags
        then text mod_str
        else hsep $
           [ text (mod_str ++ replicate (max 0 (16 - length mod_str)) ' ')
           , char '('
           , text (moduleNodeInfoSource mni) <> char ','
           , moduleNodeInfoExtraMessage mni, char ')' ]
    where
      mod_str  = moduleNameString (moduleName (moduleNodeInfoModule mni)) ++
                 moduleNodeInfoBootString mni
  
  -- | Extra information about a 'ModuleNodeInfo' to display in the progress message.
  moduleNodeInfoExtraMessage :: ModuleNodeInfo -> SDoc
  moduleNodeInfoExtraMessage (ModuleNodeCompile mod_summary) =
      let dyn_file = normalise $ msDynObjFilePath mod_summary
          obj_file = normalise $ msObjFilePath mod_summary
          bc_file  = normalise $ msBytecodeFilePath mod_summary
          dflags   = ms_hspp_opts mod_summary
          bc_message = if gopt Opt_WriteByteCode dflags then bc_file else "interpreted"
          files    = [ obj_file | backendWritesFiles (backend dflags) ]
                     ++ [ dyn_file | backendWritesFiles (backend dflags) && gopt Opt_BuildDynamicToo dflags ]
                     ++ [ bc_message | (backendWritesFiles (backend dflags) && gopt Opt_ByteCodeAndObjectCode dflags) || backendWritesBytecodeFiles (backend dflags) ]
      in case files of
           [] -> text "nothing"
           _  -> hsep $ punctuate comma (map text files)
  moduleNodeInfoExtraMessage (ModuleNodeFixed {}) = text "fixed"
  
  
  -- | The source location of the module node to show to the user.
  moduleNodeInfoSource :: ModuleNodeInfo -> FilePath
  moduleNodeInfoSource (ModuleNodeCompile ms) = normalise $ msHsFilePath ms
  moduleNodeInfoSource (ModuleNodeFixed _ loc) = normalise $ ml_hi_file loc
  
  -- | The extra info about a module [boot] or [sig] to display.
  moduleNodeInfoBootString :: ModuleNodeInfo -> String
  moduleNodeInfoBootString (ModuleNodeCompile ms) = hscSourceString (ms_hsc_src ms)
  moduleNodeInfoBootString mn@(ModuleNodeFixed {}) =
    hscSourceString (case isBootModuleNodeInfo mn of
                        IsBoot -> HsBootFile
                        NotBoot -> HsSrcFile)
  
  --------------------------------------------------------------------------------
  -- * Internal methods for module graph
  --
  -- These are *really* meant to be internal!
  -- Don't expose them without careful consideration about the invariants
  -- described in the export list haddocks.
  --------------------------------------------------------------------------------
  
  newtype NodeMap a = NodeMap { unNodeMap :: Map.Map NodeKey a }
    deriving (Functor, Traversable, Foldable)
  
  -- | Transitive dependencies, including SOURCE edges
  mkTransDeps :: [ModuleGraphNode] -> (ReachabilityIndex SummaryNode, NodeKey -> Maybe SummaryNode)
  mkTransDeps = first graphReachability {- module graph is acyclic -} . moduleGraphNodes False
  
  -- | Transitive dependencies, ignoring SOURCE edges
  mkTransLoopDeps :: [ModuleGraphNode] -> (ReachabilityIndex SummaryNode, NodeKey -> Maybe SummaryNode)
  mkTransLoopDeps = first cyclicGraphReachability . moduleGraphNodes True
  
  -- | Transitive dependencies, but only following "normal" level 0 imports.
  -- This graph can be used to query what the transitive dependencies of a particular
  -- level are within a module.
  mkTransZeroDeps :: [ModuleGraphNode] -> (ReachabilityIndex ZeroSummaryNode, ZeroScopeKey -> Maybe ZeroSummaryNode)
  mkTransZeroDeps = first graphReachability {- module graph is acyclic -} . moduleGraphNodesZero
  
  -- | Transitive dependencies, but with the stage that each module is required at.
  mkStageDeps :: [ModuleGraphNode] -> (ReachabilityIndex StageSummaryNode, (NodeKey, ModuleStage) -> Maybe StageSummaryNode)
  mkStageDeps = first cyclicGraphReachability . moduleGraphNodesStages
  
  type ZeroSummaryNode = Node Int ZeroScopeKey
  
  zeroSummaryNodeKey :: ZeroSummaryNode -> Int
  zeroSummaryNodeKey = node_key
  
  zeroSummaryNodeSummary :: ZeroSummaryNode -> ZeroScopeKey
  zeroSummaryNodeSummary = node_payload
  
  -- | The 'ZeroScopeKey' indicates the different scopes which we can refer to in a zero-scope query.
  data ZeroScopeKey = ModuleScope ModNodeKeyWithUid ImportLevel | UnitScope UnitId
    deriving (Eq, Ord)
  
  instance Outputable ZeroScopeKey where
    ppr (ModuleScope mk il) = text "ModuleScope" <+> ppr mk <+> ppr il
    ppr (UnitScope uid) = text "UnitScope" <+> ppr uid
  
  -- | Turn a list of graph nodes into an efficient queriable graph.
  -- This graph only has edges between level-0 imports
  --
  -- This query answers the question. If I am looking at level n in module M then which
  -- modules are visible?
  --
  -- If you are looking at level -1  then the reachable modules are those imported at splice and
  -- then any modules those modules import at zero. (Ie the zero scope for those modules)
  moduleGraphNodesZero ::
       [ModuleGraphNode]
    -> (Graph ZeroSummaryNode, ZeroScopeKey -> Maybe ZeroSummaryNode)
  moduleGraphNodesZero summaries =
    (graphFromEdgedVerticesUniq nodes, lookup_node)
    where
      nodes = mapMaybe go numbered_summaries
  
        where
          go :: (((ModuleGraphNode, ImportLevel)), Int) -> Maybe ZeroSummaryNode
          go (((ModuleNode nks ms), s), key) = Just $
                 DigraphNode (ModuleScope (mnKey ms) s) key $ out_edge_keys $
                      mapMaybe (classifyDeps s) nks
          go (((UnitNode uids uid), _s), key) =
            Just $ DigraphNode (UnitScope uid) key (mapMaybe lookup_key $ map UnitScope uids)
          go _ = Nothing
  
      -- This is the key part, a dependency edge also depends on the NormalLevel scope of an import.
      classifyDeps s (ModuleNodeEdge il (NodeKey_Module k)) | s == il = Just (ModuleScope k NormalLevel)
      classifyDeps s (ModuleNodeEdge il (NodeKey_ExternalUnit u)) | s == il = Just (UnitScope u)
      classifyDeps _ _ = Nothing
  
      numbered_summaries :: [((ModuleGraphNode, ImportLevel), Int)]
      numbered_summaries = zip (([(s, l) | s <- summaries, l <- [SpliceLevel, QuoteLevel, NormalLevel]])) [0..]
  
      lookup_node :: ZeroScopeKey -> Maybe ZeroSummaryNode
      lookup_node key = Map.lookup key node_map
  
      lookup_key :: ZeroScopeKey -> Maybe Int
      lookup_key = fmap zeroSummaryNodeKey . lookup_node
  
      node_map :: Map.Map ZeroScopeKey ZeroSummaryNode
      node_map =
        Map.fromList [ (s, node)
                     | node <- nodes
                     , let s = zeroSummaryNodeSummary node
                     ]
  
      out_edge_keys :: [ZeroScopeKey] -> [Int]
      out_edge_keys = mapMaybe lookup_key
  
  type StageSummaryNode = Node Int (NodeKey, ModuleStage)
  
  stageSummaryNodeKey :: StageSummaryNode -> Int
  stageSummaryNodeKey = node_key
  
  stageSummaryNodeSummary :: StageSummaryNode -> (NodeKey, ModuleStage)
  stageSummaryNodeSummary = node_payload
  
  -- | Turn a list of graph nodes into an efficient queriable graph.
  -- This graph has edges between modules and the stage they are required at.
  --
  -- This graph can be used to answer the query, if I am compiling a module at stage
  -- S, then what modules do I need at which stages for that?
  -- Used by 'downsweep' in order to determine which modules need code generation if you
  -- are using 'TemplateHaskell'.
  --
  -- The rules for this query can be read in more detail in the Explicit Level Imports proposal.
  -- Briefly:
  --  * If NoImplicitStagePersistence then Quote/Splice/Normal imports offset the required stage
  --  * If ImplicitStagePersistence and TemplateHaskell then imported module are needed at all stages.
  --  * Otherwise, an imported module is just needed at the normal stage.
  --
  --  * A module using TemplateHaskellQuotes required at C stage is also required at R
  --    stage.
  moduleGraphNodesStages ::
       [ModuleGraphNode]
    -> (Graph StageSummaryNode, (NodeKey, ModuleStage) -> Maybe StageSummaryNode)
  moduleGraphNodesStages summaries =
    (graphFromEdgedVerticesUniq nodes, lookup_node)
    where
      nodes = map go numbered_summaries
  
        where
          go :: (((ModuleGraphNode, ModuleStage)), Int) -> StageSummaryNode
          go (s, key) = normal_case s
            where
             normal_case :: (ModuleGraphNode, ModuleStage)  -> StageSummaryNode
             normal_case ((m@(ModuleNode nks ms), s)) =
                    DigraphNode ((mkNodeKey m, s)) key $ out_edge_keys $
                         selfEdges ms s (mkNodeKey m) ++ concatMap (classifyDeps ms s) nks
             normal_case (m, s) =
               DigraphNode (mkNodeKey m, s) key (out_edge_keys . map (, s) $ mgNodeDependencies False m)
  
      isExplicitStageMS :: ModSummary -> Bool
      isExplicitStageMS ms = not (xopt LangExt.ImplicitStagePersistence (ms_hspp_opts ms))
  
      isTemplateHaskellQuotesMS :: ModSummary -> Bool
      isTemplateHaskellQuotesMS ms = xopt LangExt.TemplateHaskellQuotes (ms_hspp_opts ms)
  
      -- Accounting for persistence within a module.
      -- If a module is required @ C and it persists an idenfifier, it's also required
      -- at R.
      selfEdges (ModuleNodeCompile ms) s self_key
        | not (isExplicitStageMS ms)
          && (isTemplateHaskellQuotesMS ms
              || isTemplateHaskellOrQQNonBoot ms)
          = [(self_key, s') | s' <- onlyFutureStages s]
      selfEdges _ _ _ = []
  
      -- Case 1. No implicit stage persistnce is enabled
      classifyDeps (ModuleNodeCompile ms) s (ModuleNodeEdge il k)
        | isExplicitStageMS ms = case il of
                                  SpliceLevel -> [(k, decModuleStage s)]
                                  NormalLevel -> [(k, s)]
                                  QuoteLevel  -> [(k, incModuleStage s)]
      -- Case 2a. TemplateHaskellQuotes case  (section 5.6 in the paper)
      classifyDeps (ModuleNodeCompile ms) s (ModuleNodeEdge _ k)
        | not (isExplicitStageMS ms)
        , not (isTemplateHaskellOrQQNonBoot ms)
        , isTemplateHaskellQuotesMS ms
        = [(k, s') | s' <- nowAndFutureStages s]
      -- Case 2b. Template haskell is enabled, with implicit stage persistence
      classifyDeps (ModuleNodeCompile ms) _ (ModuleNodeEdge _ k)
        | isTemplateHaskellOrQQNonBoot ms
        , not (isExplicitStageMS ms) =
          [(k, s) | s <- allStages]
      -- Case 3. No template haskell, therefore no additional dependencies.
      classifyDeps _ s (ModuleNodeEdge _ k) = [(k, s)]
  
  
      numbered_summaries :: [((ModuleGraphNode, ModuleStage), Int)]
      numbered_summaries = zip (([(s, l) | s <- summaries, l <- allStages])) [0..]
  
      lookup_node :: (NodeKey, ModuleStage) -> Maybe StageSummaryNode
      lookup_node key = Map.lookup key node_map
  
      lookup_key ::  (NodeKey, ModuleStage) -> Maybe Int
      lookup_key = fmap stageSummaryNodeKey . lookup_node
  
      node_map :: Map.Map (NodeKey, ModuleStage) StageSummaryNode
      node_map =
        Map.fromList [ (s, node)
                     | node <- nodes
                     , let s = stageSummaryNodeSummary node
                     ]
  
      out_edge_keys :: [(NodeKey, ModuleStage)] -> [Int]
      out_edge_keys = mapMaybe lookup_key
          -- If we want keep_hi_boot_nodes, then we do lookup_key with
          -- IsBoot; else False
  
  
  -- | Add an ExtendedModSummary to ModuleGraph. Assumes that the new ModSummary is
  -- not an element of the ModuleGraph.
  extendMG :: ModuleGraph -> ModuleGraphNode -> ModuleGraph
  extendMG ModuleGraph{..} node =
    ModuleGraph
      { mg_mss = node : mg_mss
      , mg_graph =  mkTransDeps (node : mg_mss)
      , mg_loop_graph = mkTransLoopDeps (node : mg_mss)
      , mg_zero_graph = mkTransZeroDeps (node : mg_mss)
      , mg_has_holes = mg_has_holes || maybe False isHsigFile (moduleNodeInfoHscSource =<< mgNodeIsModule node)
      }
  
  