content: {-# LANGUAGE DeriveFunctor #-}
  {-# LANGUAGE DeriveTraversable #-}
  {-# LANGUAGE DeriveFoldable #-}
  
  -- | See <https://github.com/ezyang/ghc-proposals/blob/backpack/proposals/0000-backpack.rst>
  module Distribution.Backpack.ModuleScope (
      -- * Module scopes
      ModuleScope(..),
      ModuleProvides,
      ModuleRequires,
      ModuleSource(..),
      dispModuleSource,
      WithSource(..),
      unWithSource,
      getSource,
      ModuleWithSource,
      emptyModuleScope,
  ) where
  
  import Prelude ()
  import Distribution.Compat.Prelude
  
  import Distribution.ModuleName
  import Distribution.Types.IncludeRenaming
  import Distribution.Types.PackageName
  import Distribution.Types.ComponentName
  
  import Distribution.Backpack
  import Distribution.Backpack.ModSubst
  import Distribution.Text
  
  import qualified Data.Map as Map
  import Text.PrettyPrint
  
  -----------------------------------------------------------------------
  -- Module scopes
  
  -- Why is ModuleProvides so complicated?  The basic problem is that
  -- we want to support this:
  --
  --  package p where
  --      include q (A)
  --      include r (A)
  --      module B where
  --          import "q" A
  --          import "r" A
  --
  -- Specifically, in Cabal today it is NOT an error have two modules in
  -- scope with the same identifier.  So we need to preserve this for
  -- Backpack.  The modification is that an ambiguous module name is
  -- OK... as long as it is NOT used to fill a requirement!
  --
  -- So as a first try, we might try deferring unifying provisions that
  -- are being glommed together, and check for equality after the fact.
  -- But this doesn't work, because what if a multi-module provision
  -- is used to fill a requirement?!  So you do the equality test
  -- IMMEDIATELY before a requirement fill happens... or never at all.
  --
  -- Alternate strategy: go ahead and unify, and then if it is revealed
  -- that some requirements got filled "out-of-thin-air", error.
  
  
  -- | A 'ModuleScope' describes the modules and requirements that
  -- are in-scope as we are processing a Cabal package.  Unlike
  -- a 'ModuleShape', there may be multiple modules in scope at
  -- the same 'ModuleName'; this is only an error if we attempt
  -- to use those modules to fill a requirement.  A 'ModuleScope'
  -- can influence the 'ModuleShape' via a reexport.
  data ModuleScope = ModuleScope {
      modScopeProvides :: ModuleProvides,
      modScopeRequires :: ModuleRequires
      }
  
  -- | An empty 'ModuleScope'.
  emptyModuleScope :: ModuleScope
  emptyModuleScope = ModuleScope Map.empty Map.empty
  
  -- | Every 'Module' in scope at a 'ModuleName' is annotated with
  -- the 'PackageName' it comes from.
  type ModuleProvides = Map ModuleName [ModuleWithSource]
  -- | INVARIANT: entries for ModuleName m, have msrc_module is OpenModuleVar m
  type ModuleRequires = Map ModuleName [ModuleWithSource]
  -- TODO: consider newtping the two types above.
  
  -- | Description of where a module participating in mixin linking came
  -- from.
  data ModuleSource
      = FromMixins         PackageName ComponentName IncludeRenaming
      | FromBuildDepends   PackageName ComponentName
      | FromExposedModules ModuleName
      | FromOtherModules   ModuleName
      | FromSignatures     ModuleName
  -- We don't have line numbers, but if we did, we'd want to record that
  -- too
  -- TODO: Deduplicate this with Distribution.Backpack.UnifyM.ci_msg
  dispModuleSource :: ModuleSource -> Doc
  dispModuleSource (FromMixins pn cn incls)
    = text "mixins:" <+> dispComponent pn cn <+> disp incls
  dispModuleSource (FromBuildDepends pn cn)
    = text "build-depends:" <+> dispComponent pn cn
  dispModuleSource (FromExposedModules m)
    = text "exposed-modules:" <+> disp m
  dispModuleSource (FromOtherModules m)
    = text "other-modules:" <+> disp m
  dispModuleSource (FromSignatures m)
    = text "signatures:" <+> disp m
  
  -- Dependency
  dispComponent :: PackageName -> ComponentName -> Doc
  dispComponent pn cn =
      -- NB: This syntax isn't quite the source syntax, but it
      -- should be clear enough.  To do source syntax, we'd
      -- need to know what the package we're linking is.
      case cn of
          CLibName -> disp pn
          CSubLibName ucn -> disp pn <<>> colon <<>> disp ucn
          -- Case below shouldn't happen
          _ -> disp pn <+> parens (disp cn)
  
  -- | An 'OpenModule', annotated with where it came from in a Cabal file.
  data WithSource a = WithSource ModuleSource a
      deriving (Functor, Foldable, Traversable)
  unWithSource :: WithSource a -> a
  unWithSource (WithSource _ x) = x
  getSource :: WithSource a -> ModuleSource
  getSource (WithSource s _) = s
  type ModuleWithSource = WithSource OpenModule
  
  instance ModSubst a => ModSubst (WithSource a) where
      modSubst subst (WithSource s m) = WithSource s (modSubst subst m)
  