From a5a823d47b44802e535dbdf3d225da706b78f3bf Mon Sep 17 00:00:00 2001
From: Edsko de Vries <edsko@well-typed.com>
Date: Fri, 27 Mar 2015 14:33:02 +0000
Subject: [PATCH] Fine-grained dependencies in solver input

The modular solver has its own representation for a package (PInfo). In this
commit we modify PInfo to keep track of the different kinds of dependencies.

This is a bit intricate because the solver also regards top-level goals as
dependencies, but of course those dependencies are not part of any 'component'
as such, unlike "real" dependencies. We model this by adding a type parameter
to FlaggedDeps and go which indicates whether or not we have component
information; crucially, underneath flag choices we _always_ have component
information available.

Consequently, the modular solver itself will not make use of the ComponentDeps
datatype (but only using the Component type, classifying components); we will
use ComponentDeps when we translate out of the results from the modular solver
into cabal-install's main datatypes.

We don't yet _return_ fine-grained dependencies from the solver; this will be
the subject of the next commit.
---
 .../Client/Dependency/Modular/Builder.hs      | 51 +++++++------
 .../Client/Dependency/Modular/Dependency.hs   | 73 ++++++++++++++++---
 .../Client/Dependency/Modular/Index.hs        |  4 +-
 .../Dependency/Modular/IndexConversion.hs     | 57 +++++++++------
 .../Client/Dependency/Modular/Linking.hs      | 17 +++--
 .../Client/Dependency/Modular/Preference.hs   |  8 +-
 .../Client/Dependency/Modular/Tree.hs         | 16 ++--
 .../Client/Dependency/Modular/Validate.hs     | 13 ++--
 8 files changed, 158 insertions(+), 81 deletions(-)

diff --git a/cabal-install/Distribution/Client/Dependency/Modular/Builder.hs b/cabal-install/Distribution/Client/Dependency/Modular/Builder.hs
index 1a9bb2cd34..3c6e4b082e 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/Builder.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/Builder.hs
@@ -27,38 +27,42 @@ import Distribution.Client.Dependency.Modular.Package
 import Distribution.Client.Dependency.Modular.PSQ as P
 import Distribution.Client.Dependency.Modular.Tree
 
+import Distribution.Client.ComponentDeps (Component)
+
 -- | The state needed during the build phase of the search tree.
 data BuildState = BS {
-  index :: Index,           -- ^ information about packages and their dependencies
-  rdeps :: RevDepMap,       -- ^ set of all package goals, completed and open, with reverse dependencies
-  open  :: PSQ OpenGoal (), -- ^ set of still open goals (flag and package goals)
-  next  :: BuildType        -- ^ kind of node to generate next
+  index :: Index,                -- ^ information about packages and their dependencies
+  rdeps :: RevDepMap,            -- ^ set of all package goals, completed and open, with reverse dependencies
+  open  :: PSQ (OpenGoal ()) (), -- ^ set of still open goals (flag and package goals)
+  next  :: BuildType             -- ^ kind of node to generate next
 }
 
 -- | Extend the set of open goals with the new goals listed.
 --
 -- We also adjust the map of overall goals, and keep track of the
 -- reverse dependencies of each of the goals.
-extendOpen :: QPN -> [OpenGoal] -> BuildState -> BuildState
+extendOpen :: QPN -> [OpenGoal Component] -> BuildState -> BuildState
 extendOpen qpn' gs s@(BS { rdeps = gs', open = o' }) = go gs' o' gs
   where
-    go :: RevDepMap -> PSQ OpenGoal () -> [OpenGoal] -> BuildState
-    go g o []                                             = s { rdeps = g, open = o }
-    go g o (ng@(OpenGoal (Flagged _ _ _ _)    _gr) : ngs) = go g (cons ng () o) ngs
+    go :: RevDepMap -> PSQ (OpenGoal ()) () -> [OpenGoal Component] -> BuildState
+    go g o []                                               = s { rdeps = g, open = o }
+    go g o (ng@(OpenGoal (Flagged _ _ _ _)      _gr) : ngs) = go g (cons' ng () o) ngs
       -- Note: for 'Flagged' goals, we always insert, so later additions win.
       -- This is important, because in general, if a goal is inserted twice,
       -- the later addition will have better dependency information.
-    go g o (ng@(OpenGoal (Stanza  _   _  )    _gr) : ngs) = go g (cons ng () o) ngs
-    go g o (ng@(OpenGoal (Simple (Dep qpn _)) _gr) : ngs)
-      | qpn == qpn'                                       = go                       g              o  ngs
-                                       -- we ignore self-dependencies at this point; TODO: more care may be needed
-      | qpn `M.member` g                                  = go (M.adjust (qpn':) qpn g)             o  ngs
-      | otherwise                                         = go (M.insert qpn [qpn']  g) (cons ng () o) ngs
-                                       -- code above is correct; insert/adjust have different arg order
+    go g o (ng@(OpenGoal (Stanza  _   _  )      _gr) : ngs) = go g (cons' ng () o) ngs
+    go g o (ng@(OpenGoal (Simple (Dep qpn _) _) _gr) : ngs)
+      | qpn == qpn'       = go                       g               o  ngs
+          -- we ignore self-dependencies at this point; TODO: more care may be needed
+      | qpn `M.member` g  = go (M.adjust (qpn':) qpn g)              o  ngs
+      | otherwise         = go (M.insert qpn [qpn']  g) (cons' ng () o) ngs
+          -- code above is correct; insert/adjust have different arg order
+
+    cons' = cons . forgetCompOpenGoal
 
 -- | Given the current scope, qualify all the package names in the given set of
 -- dependencies and then extend the set of open goals accordingly.
-scopedExtendOpen :: QPN -> I -> QGoalReasonChain -> FlaggedDeps PN -> FlagInfo ->
+scopedExtendOpen :: QPN -> I -> QGoalReasonChain -> FlaggedDeps Component PN -> FlagInfo ->
                     BuildState -> BuildState
 scopedExtendOpen qpn@(Q pp _pn) i gr fdeps fdefs s = extendOpen qpn gs s
   where
@@ -87,7 +91,7 @@ scopedExtendOpen qpn@(Q pp _pn) i gr fdeps fdefs s = extendOpen qpn gs s
 -- | Datatype that encodes what to build next
 data BuildType =
     Goals                                  -- ^ build a goal choice node
-  | OneGoal OpenGoal                       -- ^ build a node for this goal
+  | OneGoal (OpenGoal ())                  -- ^ build a node for this goal
   | Instance QPN I PInfo QGoalReasonChain  -- ^ build a tree for a concrete instance
   deriving Show
 
@@ -109,7 +113,7 @@ build = ana go
     --
     -- For a package, we look up the instances available in the global info,
     -- and then handle each instance in turn.
-    go bs@(BS { index = idx, next = OneGoal (OpenGoal (Simple (Dep qpn@(Q _ pn) _)) gr) }) =
+    go bs@(BS { index = idx, next = OneGoal (OpenGoal (Simple (Dep qpn@(Q _ pn) _) _) gr) }) =
       case M.lookup pn idx of
         Nothing  -> FailF (toConflictSet (Goal (P qpn) gr)) (BuildFailureNotInIndex pn)
         Just pis -> PChoiceF qpn gr (P.fromList (L.map (\ (i, info) ->
@@ -149,9 +153,14 @@ build = ana go
 -- and computes the initial state and then the tree from there.
 buildTree :: Index -> Bool -> [PN] -> Tree QGoalReasonChain
 buildTree idx ind igs =
-    build (BS idx (M.fromList (L.map (\ qpn -> (qpn, []))                                                     qpns))
-                  (P.fromList (L.map (\ qpn -> (OpenGoal (Simple (Dep qpn (Constrained []))) [UserGoal], ())) qpns))
-                  Goals)
+    build BS {
+        index = idx
+      , rdeps = M.fromList (L.map (\ qpn -> (qpn, []))              qpns)
+      , open  = P.fromList (L.map (\ qpn -> (topLevelGoal qpn, ())) qpns)
+      , next  = Goals
+      }
   where
+    topLevelGoal qpn = OpenGoal (Simple (Dep qpn (Constrained [])) ()) [UserGoal]
+
     qpns | ind       = makeIndependent igs
          | otherwise = L.map (Q None) igs
diff --git a/cabal-install/Distribution/Client/Dependency/Modular/Dependency.hs b/cabal-install/Distribution/Client/Dependency/Modular/Dependency.hs
index 5282de4d71..95d59f5ed0 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/Dependency.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/Dependency.hs
@@ -18,6 +18,9 @@ module Distribution.Client.Dependency.Modular.Dependency (
   , FalseFlaggedDeps
   , Dep(..)
   , showDep
+    -- ** Setting/forgetting components
+  , forgetCompOpenGoal
+  , setCompFlaggedDeps
     -- * Reverse dependency map
   , RevDepMap
     -- * Goals
@@ -48,6 +51,8 @@ import Distribution.Client.Dependency.Modular.Flag
 import Distribution.Client.Dependency.Modular.Package
 import Distribution.Client.Dependency.Modular.Version
 
+import Distribution.Client.ComponentDeps (Component)
+
 {-------------------------------------------------------------------------------
   Variables
 -------------------------------------------------------------------------------}
@@ -133,18 +138,37 @@ merge   (Constrained rs)     (Constrained ss) = Right (Constrained (rs ++ ss))
   Flagged dependencies
 -------------------------------------------------------------------------------}
 
-type FlaggedDeps qpn = [FlaggedDep qpn]
+-- | Flagged dependencies
+--
+-- 'FlaggedDeps' is the modular solver's view of a packages dependencies:
+-- rather than having the dependencies indexed by component, each dependency
+-- defines what component it is in.
+--
+-- However, top-level goals are also modelled as dependencies, but of course
+-- these don't actually belong in any component of any package. Therefore, we
+-- parameterize 'FlaggedDeps' and derived datatypes with a type argument that
+-- specifies whether or not we have a component: we only ever instantiate this
+-- type argument with @()@ for top-level goals, or 'Component' for everything
+-- else (we could express this as a kind at the type-level, but that would
+-- require a very recent GHC).
+--
+-- Note however, crucially, that independent of the type parameters, the list
+-- of dependencies underneath a flag choice or stanza choices _always_ uses
+-- Component as the type argument. This is important: when we pick a value for
+-- a flag, we _must_ know what component the new dependencies belong to, or
+-- else we don't be able to construct fine-grained reverse dependencies.
+type FlaggedDeps comp qpn = [FlaggedDep comp qpn]
 
 -- | Flagged dependencies can either be plain dependency constraints,
 -- or flag-dependent dependency trees.
-data FlaggedDep qpn =
+data FlaggedDep comp qpn =
     Flagged (FN qpn) FInfo (TrueFlaggedDeps qpn) (FalseFlaggedDeps qpn)
   | Stanza  (SN qpn)       (TrueFlaggedDeps qpn)
-  | Simple (Dep qpn)
+  | Simple (Dep qpn) comp
   deriving (Eq, Show, Functor)
 
-type TrueFlaggedDeps  qpn = FlaggedDeps qpn
-type FalseFlaggedDeps qpn = FlaggedDeps qpn
+type TrueFlaggedDeps  qpn = FlaggedDeps Component qpn
+type FalseFlaggedDeps qpn = FlaggedDeps Component qpn
 
 -- | A dependency (constraint) associates a package name with a
 -- constrained instance.
@@ -160,6 +184,35 @@ showDep (Dep qpn (Constrained [(vr, Goal v _)])) =
 showDep (Dep qpn ci                            ) =
   showQPN qpn ++ showCI ci
 
+{-------------------------------------------------------------------------------
+  Setting/forgetting the Component
+-------------------------------------------------------------------------------}
+
+forgetCompOpenGoal :: OpenGoal Component -> OpenGoal ()
+forgetCompOpenGoal = mapCompOpenGoal $ const ()
+
+setCompFlaggedDeps :: Component -> FlaggedDeps () qpn -> FlaggedDeps Component qpn
+setCompFlaggedDeps = mapCompFlaggedDeps . const
+
+{-------------------------------------------------------------------------------
+  Auxiliary: Mapping over the Component goal
+
+  We don't export these, because the only type instantiations for 'a' and 'b'
+  here should be () or Component. (We could express this at the type level
+  if we relied on newer versions of GHC.)
+-------------------------------------------------------------------------------}
+
+mapCompOpenGoal :: (a -> b) -> OpenGoal a -> OpenGoal b
+mapCompOpenGoal g (OpenGoal d gr) = OpenGoal (mapCompFlaggedDep g d) gr
+
+mapCompFlaggedDeps :: (a -> b) -> FlaggedDeps a qpn -> FlaggedDeps b qpn
+mapCompFlaggedDeps = L.map . mapCompFlaggedDep
+
+mapCompFlaggedDep :: (a -> b) -> FlaggedDep a qpn -> FlaggedDep b qpn
+mapCompFlaggedDep _ (Flagged fn nfo t f) = Flagged fn nfo   t f
+mapCompFlaggedDep _ (Stanza  sn     t  ) = Stanza  sn       t
+mapCompFlaggedDep g (Simple  pn a      ) = Simple  pn (g a)
+
 {-------------------------------------------------------------------------------
   Reverse dependency map
 -------------------------------------------------------------------------------}
@@ -227,15 +280,15 @@ goalReasonChainsToVars = S.unions . L.map goalReasonChainToVars
 
 -- | For open goals as they occur during the build phase, we need to store
 -- additional information about flags.
-data OpenGoal = OpenGoal (FlaggedDep QPN) QGoalReasonChain
+data OpenGoal comp = OpenGoal (FlaggedDep comp QPN) QGoalReasonChain
   deriving (Eq, Show)
 
 -- | Closes a goal, i.e., removes all the extraneous information that we
 -- need only during the build phase.
-close :: OpenGoal -> Goal QPN
-close (OpenGoal (Simple (Dep qpn _)) gr) = Goal (P qpn) gr
-close (OpenGoal (Flagged qfn _ _ _ ) gr) = Goal (F qfn) gr
-close (OpenGoal (Stanza  qsn _)      gr) = Goal (S qsn) gr
+close :: OpenGoal comp -> Goal QPN
+close (OpenGoal (Simple (Dep qpn _) _) gr) = Goal (P qpn) gr
+close (OpenGoal (Flagged qfn _ _ _ )   gr) = Goal (F qfn) gr
+close (OpenGoal (Stanza  qsn _)        gr) = Goal (S qsn) gr
 
 {-------------------------------------------------------------------------------
   Version ranges paired with origins
diff --git a/cabal-install/Distribution/Client/Dependency/Modular/Index.hs b/cabal-install/Distribution/Client/Dependency/Modular/Index.hs
index ac3450379a..9af767aa97 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/Index.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/Index.hs
@@ -9,6 +9,8 @@ import Distribution.Client.Dependency.Modular.Flag
 import Distribution.Client.Dependency.Modular.Package
 import Distribution.Client.Dependency.Modular.Tree
 
+import Distribution.Client.ComponentDeps (Component)
+
 -- | An index contains information about package instances. This is a nested
 -- dictionary. Package names are mapped to instances, which in turn is mapped
 -- to info.
@@ -20,7 +22,7 @@ type Index = Map PN (Map I PInfo)
 -- globally, for reasons external to the solver. We currently use this
 -- for shadowing which essentially is a GHC limitation, and for
 -- installed packages that are broken.
-data PInfo = PInfo (FlaggedDeps PN) FlagInfo (Maybe FailReason)
+data PInfo = PInfo (FlaggedDeps Component PN) FlagInfo (Maybe FailReason)
   deriving (Show)
 
 mkIndex :: [(PN, I, PInfo)] -> Index
diff --git a/cabal-install/Distribution/Client/Dependency/Modular/IndexConversion.hs b/cabal-install/Distribution/Client/Dependency/Modular/IndexConversion.hs
index 53b5a46a4d..681a6d3014 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/IndexConversion.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/IndexConversion.hs
@@ -21,6 +21,8 @@ import Distribution.Client.Dependency.Modular.Package
 import Distribution.Client.Dependency.Modular.Tree
 import Distribution.Client.Dependency.Modular.Version
 
+import Distribution.Client.ComponentDeps (Component(..))
+
 -- | Convert both the installed package index and the source package
 -- index into one uniform solver index.
 --
@@ -62,8 +64,11 @@ convIP idx ipi =
       i = I (pkgVersion (sourcePackageId ipi)) (Inst ipid)
       pn = pkgName (sourcePackageId ipi)
   in  case mapM (convIPId pn idx) (IPI.depends ipi) of
-        Nothing  -> (pn, i, PInfo []  M.empty (Just Broken))
-        Just fds -> (pn, i, PInfo fds M.empty Nothing)
+        Nothing  -> (pn, i, PInfo []            M.empty (Just Broken))
+        Just fds -> (pn, i, PInfo (setComp fds) M.empty Nothing)
+ where
+  -- We assume that all dependencies of installed packages are _library_ deps
+  setComp = setCompFlaggedDeps ComponentLib
 -- TODO: Installed packages should also store their encapsulations!
 
 -- | Convert dependencies specified by an installed package id into
@@ -72,13 +77,13 @@ convIP idx ipi =
 -- May return Nothing if the package can't be found in the index. That
 -- indicates that the original package having this dependency is broken
 -- and should be ignored.
-convIPId :: PN -> SI.InstalledPackageIndex -> InstalledPackageId -> Maybe (FlaggedDep PN)
+convIPId :: PN -> SI.InstalledPackageIndex -> InstalledPackageId -> Maybe (FlaggedDep () PN)
 convIPId pn' idx ipid =
   case SI.lookupInstalledPackageId idx ipid of
     Nothing  -> Nothing
     Just ipi -> let i = I (pkgVersion (sourcePackageId ipi)) (Inst ipid)
                     pn = pkgName (sourcePackageId ipi)
-                in  Just (D.Simple (Dep pn (Fixed i (Goal (P pn') []))))
+                in  Just (D.Simple (Dep pn (Fixed i (Goal (P pn') []))) ())
 
 -- | Convert a cabal-install source package index to the simpler,
 -- more uniform index format of the solver.
@@ -101,27 +106,25 @@ convSP os arch cinfo strfl (SourcePackage (PackageIdentifier pn pv) gpd _ _pl) =
 -- want to keep the condition tree, but simplify much of the test.
 
 -- | Convert a generic package description to a solver-specific 'PInfo'.
---
--- TODO: We currently just take all dependencies from all specified library,
--- executable and test components. This does not quite seem fair.
 convGPD :: OS -> Arch -> CompilerInfo -> Bool ->
            PI PN -> GenericPackageDescription -> PInfo
 convGPD os arch comp strfl pi
         (GenericPackageDescription _ flags libs exes tests benchs) =
   let
-    fds = flagInfo strfl flags
+    fds  = flagInfo strfl flags
+    conv = convCondTree os arch comp pi fds (const True)
   in
     PInfo
-      (maybe []    (convCondTree os arch comp pi fds (const True)          ) libs    ++
-       concatMap   (convCondTree os arch comp pi fds (const True)     . snd) exes    ++
+      (maybe []    (conv ComponentLib                       ) libs    ++
+       concatMap   (\(nm, ds) -> conv (ComponentExe nm)   ds) exes    ++
       prefix (Stanza (SN pi TestStanzas))
-        (L.map     (convCondTree os arch comp pi fds (const True)     . snd) tests)  ++
+        (L.map     (\(nm, ds) -> conv (ComponentTest nm)  ds) tests)  ++
       prefix (Stanza (SN pi BenchStanzas))
-        (L.map     (convCondTree os arch comp pi fds (const True)     . snd) benchs))
+        (L.map     (\(nm, ds) -> conv (ComponentBench nm) ds) benchs))
       fds
       Nothing
 
-prefix :: (FlaggedDeps qpn -> FlaggedDep qpn) -> [FlaggedDeps qpn] -> FlaggedDeps qpn
+prefix :: (FlaggedDeps comp qpn -> FlaggedDep comp' qpn) -> [FlaggedDeps comp qpn] -> FlaggedDeps comp' qpn
 prefix _ []  = []
 prefix f fds = [f (concat fds)]
 
@@ -133,10 +136,11 @@ flagInfo strfl = M.fromList . L.map (\ (MkFlag fn _ b m) -> (fn, FInfo b m (not
 -- | Convert condition trees to flagged dependencies.
 convCondTree :: OS -> Arch -> CompilerInfo -> PI PN -> FlagInfo ->
                 (a -> Bool) -> -- how to detect if a branch is active
-                CondTree ConfVar [Dependency] a -> FlaggedDeps PN
-convCondTree os arch comp pi@(PI pn _) fds p (CondNode info ds branches)
-  | p info    = L.map (D.Simple . convDep pn) ds  -- unconditional dependencies
-              ++ concatMap (convBranch os arch comp pi fds p) branches
+                Component ->
+                CondTree ConfVar [Dependency] a -> FlaggedDeps Component PN
+convCondTree os arch cinfo pi@(PI pn _) fds p comp (CondNode info ds branches)
+  | p info    = L.map (\d -> D.Simple (convDep pn d) comp) ds  -- unconditional dependencies
+              ++ concatMap (convBranch os arch cinfo pi fds p comp) branches
   | otherwise = []
 
 -- | Branch interpreter.
@@ -150,15 +154,16 @@ convCondTree os arch comp pi@(PI pn _) fds p (CondNode info ds branches)
 convBranch :: OS -> Arch -> CompilerInfo ->
               PI PN -> FlagInfo ->
               (a -> Bool) -> -- how to detect if a branch is active
+              Component ->
               (Condition ConfVar,
                CondTree ConfVar [Dependency] a,
-               Maybe (CondTree ConfVar [Dependency] a)) -> FlaggedDeps PN
-convBranch os arch cinfo pi fds p (c', t', mf') =
-  go c' (          convCondTree os arch cinfo pi fds p   t')
-        (maybe [] (convCondTree os arch cinfo pi fds p) mf')
+               Maybe (CondTree ConfVar [Dependency] a)) -> FlaggedDeps Component PN
+convBranch os arch cinfo pi fds p comp (c', t', mf') =
+  go c' (          convCondTree os arch cinfo pi fds p comp   t')
+        (maybe [] (convCondTree os arch cinfo pi fds p comp) mf')
   where
     go :: Condition ConfVar ->
-          FlaggedDeps PN -> FlaggedDeps PN -> FlaggedDeps PN
+          FlaggedDeps Component PN -> FlaggedDeps Component PN -> FlaggedDeps Component PN
     go (Lit True)  t _ = t
     go (Lit False) _ f = f
     go (CNot c)    t f = go c f t
@@ -187,8 +192,12 @@ convBranch os arch cinfo pi fds p (c', t', mf') =
     -- with deferring flag choices will then usually first resolve this package,
     -- and try an already installed version before imposing a default flag choice
     -- that might not be what we want.
-    extractCommon :: FlaggedDeps PN -> FlaggedDeps PN -> FlaggedDeps PN
-    extractCommon ps ps' = [ D.Simple (Dep pn (Constrained [])) | D.Simple (Dep pn _) <- ps, D.Simple (Dep pn' _) <- ps', pn == pn' ]
+    extractCommon :: FlaggedDeps Component PN -> FlaggedDeps Component PN -> FlaggedDeps Component PN
+    extractCommon ps ps' = [ D.Simple (Dep pn (Constrained [])) comp
+                           | D.Simple (Dep pn  _) _ <- ps
+                           , D.Simple (Dep pn' _) _ <- ps'
+                           , pn == pn'
+                           ]
 
 -- | Convert a Cabal dependency to a solver-specific dependency.
 convDep :: PN -> Dependency -> Dep PN
diff --git a/cabal-install/Distribution/Client/Dependency/Modular/Linking.hs b/cabal-install/Distribution/Client/Dependency/Modular/Linking.hs
index b9b5aea078..9a394ac5bf 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/Linking.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/Linking.hs
@@ -30,6 +30,7 @@ import Distribution.Client.Dependency.Modular.Tree
 import qualified Distribution.Client.Dependency.Modular.PSQ as P
 
 import Distribution.Client.Types (OptionalStanza(..))
+import Distribution.Client.ComponentDeps (Component)
 
 {-------------------------------------------------------------------------------
   Add linking
@@ -167,7 +168,7 @@ conflict = lift' . Left
 execUpdateState :: UpdateState () -> ValidateState -> Either Conflict ValidateState
 execUpdateState = execStateT . unUpdateState
 
-pickPOption :: QPN -> POption -> FlaggedDeps QPN -> UpdateState ()
+pickPOption :: QPN -> POption -> FlaggedDeps comp QPN -> UpdateState ()
 pickPOption qpn (POption i Nothing)    _deps = pickConcrete qpn i
 pickPOption qpn (POption i (Just pp'))  deps = pickLink     qpn i pp' deps
 
@@ -185,7 +186,7 @@ pickConcrete qpn@(Q pp _) i = do
       Just lg ->
         makeCanonical lg qpn
 
-pickLink :: QPN -> I -> PP -> FlaggedDeps QPN -> UpdateState ()
+pickLink :: QPN -> I -> PP -> FlaggedDeps comp QPN -> UpdateState ()
 pickLink qpn@(Q _ pn) i pp' deps = do
     vs <- get
     -- Find the link group for the package we are linking to, and add this package
@@ -211,11 +212,11 @@ makeCanonical lg qpn@(Q pp _) =
         let lg' = lg { lgCanon = Just pp }
         updateLinkGroup lg'
 
-linkDeps :: [Var QPN] -> PP -> FlaggedDeps QPN -> UpdateState ()
+linkDeps :: [Var QPN] -> PP -> FlaggedDeps comp QPN -> UpdateState ()
 linkDeps parents pp' = mapM_ go
   where
-    go :: FlaggedDep QPN -> UpdateState ()
-    go (Simple (Dep qpn@(Q _ pn) _)) = do
+    go :: FlaggedDep comp QPN -> UpdateState ()
+    go (Simple (Dep qpn@(Q _ pn) _) _) = do
       vs <- get
       let qpn' = Q pp' pn
           lg   = M.findWithDefault (lgSingleton qpn  Nothing) qpn  $ vsLinks vs
@@ -258,11 +259,11 @@ linkNewDeps var b = do
         linkedTo                = S.delete pp (lgMembers lg)
     forM_ (S.toList linkedTo) $ \pp' -> linkDeps (P qpn : parents) pp' newDeps
   where
-    findNewDeps :: ValidateState -> FlaggedDeps QPN -> ([Var QPN], FlaggedDeps QPN)
+    findNewDeps :: ValidateState -> FlaggedDeps comp QPN -> ([Var QPN], FlaggedDeps Component QPN)
     findNewDeps vs = concatMapUnzip (findNewDeps' vs)
 
-    findNewDeps' :: ValidateState -> FlaggedDep QPN -> ([Var QPN], FlaggedDeps QPN)
-    findNewDeps' _  (Simple _)          = ([], [])
+    findNewDeps' :: ValidateState -> FlaggedDep comp QPN -> ([Var QPN], FlaggedDeps Component QPN)
+    findNewDeps' _  (Simple _ _)        = ([], [])
     findNewDeps' vs (Flagged qfn _ t f) =
       case (F qfn == var, M.lookup qfn (vsFlags vs)) of
         (True, _)    -> ([F qfn], if b then t else f)
diff --git a/cabal-install/Distribution/Client/Dependency/Modular/Preference.hs b/cabal-install/Distribution/Client/Dependency/Modular/Preference.hs
index 8e8b98dba6..eb45b1b043 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/Preference.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/Preference.hs
@@ -254,10 +254,10 @@ preferBaseGoalChoice = trav go
     go (GoalChoiceF xs) = GoalChoiceF (P.sortByKeys preferBase xs)
     go x                = x
 
-    preferBase :: OpenGoal -> OpenGoal -> Ordering
-    preferBase (OpenGoal (Simple (Dep (Q _pp pn) _)) _) _ | unPN pn == "base" = LT
-    preferBase _ (OpenGoal (Simple (Dep (Q _pp pn) _)) _) | unPN pn == "base" = GT
-    preferBase _ _                                                            = EQ
+    preferBase :: OpenGoal comp -> OpenGoal comp -> Ordering
+    preferBase (OpenGoal (Simple (Dep (Q _pp pn) _) _) _) _ | unPN pn == "base" = LT
+    preferBase _ (OpenGoal (Simple (Dep (Q _pp pn) _) _) _) | unPN pn == "base" = GT
+    preferBase _ _                                                              = EQ
 
 -- | Transformation that sorts choice nodes so that
 -- child nodes with a small branching degree are preferred. As a
diff --git a/cabal-install/Distribution/Client/Dependency/Modular/Tree.hs b/cabal-install/Distribution/Client/Dependency/Modular/Tree.hs
index cdcd5760e7..307af38fc3 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/Tree.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/Tree.hs
@@ -14,10 +14,10 @@ import Distribution.Client.Dependency.Modular.Version
 
 -- | Type of the search tree. Inlining the choice nodes for now.
 data Tree a =
-    PChoice     QPN a           (PSQ POption  (Tree a))
-  | FChoice     QFN a Bool Bool (PSQ Bool     (Tree a)) -- Bool indicates whether it's weak/trivial, second Bool whether it's manual
-  | SChoice     QSN a Bool      (PSQ Bool     (Tree a)) -- Bool indicates whether it's trivial
-  | GoalChoice                  (PSQ OpenGoal (Tree a)) -- PSQ should never be empty
+    PChoice     QPN a           (PSQ POption       (Tree a))
+  | FChoice     QFN a Bool Bool (PSQ Bool          (Tree a)) -- Bool indicates whether it's weak/trivial, second Bool whether it's manual
+  | SChoice     QSN a Bool      (PSQ Bool          (Tree a)) -- Bool indicates whether it's trivial
+  | GoalChoice                  (PSQ (OpenGoal ()) (Tree a)) -- PSQ should never be empty
   | Done        RevDepMap
   | Fail        (ConflictSet QPN) FailReason
   deriving (Eq, Show, Functor)
@@ -57,10 +57,10 @@ data FailReason = InconsistentInitialConstraints
 
 -- | Functor for the tree type.
 data TreeF a b =
-    PChoiceF    QPN a           (PSQ POption  b)
-  | FChoiceF    QFN a Bool Bool (PSQ Bool     b)
-  | SChoiceF    QSN a Bool      (PSQ Bool     b)
-  | GoalChoiceF                 (PSQ OpenGoal b)
+    PChoiceF    QPN a           (PSQ POption       b)
+  | FChoiceF    QFN a Bool Bool (PSQ Bool          b)
+  | SChoiceF    QSN a Bool      (PSQ Bool          b)
+  | GoalChoiceF                 (PSQ (OpenGoal ()) b)
   | DoneF       RevDepMap
   | FailF       (ConflictSet QPN) FailReason
   deriving (Functor, Foldable, Traversable)
diff --git a/cabal-install/Distribution/Client/Dependency/Modular/Validate.hs b/cabal-install/Distribution/Client/Dependency/Modular/Validate.hs
index c28700e142..4d96bf280f 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/Validate.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/Validate.hs
@@ -21,6 +21,8 @@ import Distribution.Client.Dependency.Modular.Package
 import Distribution.Client.Dependency.Modular.PSQ as P
 import Distribution.Client.Dependency.Modular.Tree
 
+import Distribution.Client.ComponentDeps (Component)
+
 -- In practice, most constraints are implication constraints (IF we have made
 -- a number of choices, THEN we also have to ensure that). We call constraints
 -- that for which the preconditions are fulfilled ACTIVE. We maintain a set
@@ -74,7 +76,7 @@ import Distribution.Client.Dependency.Modular.Tree
 -- | The state needed during validation.
 data ValidateState = VS {
   index :: Index,
-  saved :: Map QPN (FlaggedDeps QPN), -- saved, scoped, dependencies
+  saved :: Map QPN (FlaggedDeps Component QPN), -- saved, scoped, dependencies
   pa    :: PreAssignment
 }
 
@@ -188,11 +190,11 @@ validate = cata go
 -- | We try to extract as many concrete dependencies from the given flagged
 -- dependencies as possible. We make use of all the flag knowledge we have
 -- already acquired.
-extractDeps :: FAssignment -> SAssignment -> FlaggedDeps QPN -> [Dep QPN]
+extractDeps :: FAssignment -> SAssignment -> FlaggedDeps comp QPN -> [Dep QPN]
 extractDeps fa sa deps = do
   d <- deps
   case d of
-    Simple sd           -> return sd
+    Simple sd _         -> return sd
     Flagged qfn _ td fd -> case M.lookup qfn fa of
                              Nothing    -> mzero
                              Just True  -> extractDeps fa sa td
@@ -205,13 +207,14 @@ extractDeps fa sa deps = do
 -- | We try to find new dependencies that become available due to the given
 -- flag or stanza choice. We therefore look for the choice in question, and then call
 -- 'extractDeps' for everything underneath.
-extractNewDeps :: Var QPN -> QGoalReasonChain -> Bool -> FAssignment -> SAssignment -> FlaggedDeps QPN -> [Dep QPN]
+extractNewDeps :: Var QPN -> QGoalReasonChain -> Bool -> FAssignment -> SAssignment -> FlaggedDeps comp QPN -> [Dep QPN]
 extractNewDeps v gr b fa sa = go
   where
+    go :: FlaggedDeps comp QPN -> [Dep QPN] -- Type annotation necessary (polymorphic recursion)
     go deps = do
       d <- deps
       case d of
-        Simple _             -> mzero
+        Simple _ _           -> mzero
         Flagged qfn' _ td fd
           | v == F qfn'      -> L.map (resetGoal (Goal v gr)) $
                                 if b then extractDeps fa sa td else extractDeps fa sa fd
-- 
GitLab