From fd5e0c65b2346f0fc7f7f35cee18596ca8674109 Mon Sep 17 00:00:00 2001
From: Andres Loeh <andres@well-typed.com>
Date: Thu, 23 Jul 2015 23:46:34 +0200
Subject: [PATCH] Track language extensions and language flavours in the
 solver.

Every package now "depends" on all language extensions
(default-extensions and other-extensions) and language flavours
(default-language and other-languages) it declares in its cabal file.

During solving, we verify that the compiler we use actually
supports selected extensions and languages. This has to be done
during solving, because flag choices can influence the declared
extensions and languages being used.

There currently is no equivalent check performed on the generated
install plans. In general, cabal-install performs a sanity check
on the solver output, checking that the solver e.g. indeed includes
all the declared dependencies of a package. There is no such
double-checking for language extensions. This is not really
problematic, as all that this change does is to make the solver
more conservative rather than less. However, having a sanity check
available might ultimately be nice to have.
---
 .../Distribution/Client/Dependency/Modular.hs |  2 +-
 .../Client/Dependency/Modular/Assignment.hs   | 29 +++++++++++----
 .../Client/Dependency/Modular/Builder.hs      |  6 ++++
 .../Client/Dependency/Modular/Dependency.hs   | 18 +++++++++-
 .../Dependency/Modular/IndexConversion.hs     | 26 ++++++++------
 .../Client/Dependency/Modular/Linking.hs      |  4 +++
 .../Client/Dependency/Modular/Solver.hs       | 11 +++---
 .../Client/Dependency/Modular/Validate.hs     | 35 ++++++++++++++-----
 8 files changed, 100 insertions(+), 31 deletions(-)

diff --git a/cabal-install/Distribution/Client/Dependency/Modular.hs b/cabal-install/Distribution/Client/Dependency/Modular.hs
index 60fde9aed7..4f356dcf17 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular.hs
@@ -37,7 +37,7 @@ modularResolver :: SolverConfig -> DependencyResolver
 modularResolver sc (Platform arch os) cinfo iidx sidx pprefs pcs pns =
   fmap (uncurry postprocess)      $ -- convert install plan
   logToProgress (maxBackjumps sc) $ -- convert log format into progress format
-  solve sc idx pprefs gcs pns
+  solve sc cinfo idx pprefs gcs pns
     where
       -- Indices have to be converted into solver-specific uniform index.
       idx    = convPIs os arch cinfo (shadowPkgs sc) (strongFlags sc) iidx sidx
diff --git a/cabal-install/Distribution/Client/Dependency/Modular/Assignment.hs b/cabal-install/Distribution/Client/Dependency/Modular/Assignment.hs
index e5a5080a37..54529d3c01 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/Assignment.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/Assignment.hs
@@ -8,6 +8,8 @@ import Data.Map as M
 import Data.Maybe
 import Prelude hiding (pi)
 
+import Language.Haskell.Extension (Extension, Language)
+
 import Distribution.PackageDescription (FlagAssignment) -- from Cabal
 import Distribution.Client.Types (OptionalStanza)
 import Distribution.Client.Utils.LabeledGraph
@@ -53,14 +55,27 @@ data PreAssignment = PA PPreAssignment FAssignment SAssignment
 --
 -- Either returns a witness of the conflict that would arise during the merge,
 -- or the successfully extended assignment.
-extend :: Var QPN -> PPreAssignment -> [Dep QPN] -> Either (ConflictSet QPN, [Dep QPN]) PPreAssignment
-extend var pa qa = foldM (\ a (Dep qpn ci) ->
-                     let ci' = M.findWithDefault (Constrained []) qpn a
-                     in  case (\ x -> M.insert qpn x a) <$> merge ci' ci of
-                           Left (c, (d, d')) -> Left  (c, L.map (Dep qpn) (simplify (P qpn) d d'))
-                           Right x           -> Right x)
-                    pa qa
+extend :: (Extension -> Bool) -- ^ is a given extension supported
+       -> (Language  -> Bool) -- ^ is a given language supported
+       -> Goal QPN
+       -> PPreAssignment -> [Dep QPN] -> Either (ConflictSet QPN, [Dep QPN]) PPreAssignment
+extend extSupported langSupported goal@(Goal var _) = foldM extendSingle
   where
+
+    extendSingle :: PPreAssignment -> Dep QPN
+                 -> Either (ConflictSet QPN, [Dep QPN]) PPreAssignment
+    extendSingle a (Ext  ext )  =
+      if extSupported  ext  then Right a
+                            else Left (toConflictSet goal, [Ext ext])
+    extendSingle a (Lang lang)  =
+      if langSupported lang then Right a
+                            else Left (toConflictSet goal, [Lang lang])
+    extendSingle a (Dep qpn ci) =
+      let ci' = M.findWithDefault (Constrained []) qpn a
+      in  case (\ x -> M.insert qpn x a) <$> merge ci' ci of
+            Left (c, (d, d')) -> Left  (c, L.map (Dep qpn) (simplify (P qpn) d d'))
+            Right x           -> Right x
+
     -- We're trying to remove trivial elements of the conflict. If we're just
     -- making a choice pkg == instance, and pkg => pkg == instance is a part
     -- of the conflict, then this info is clear from the context and does not
diff --git a/cabal-install/Distribution/Client/Dependency/Modular/Builder.hs b/cabal-install/Distribution/Client/Dependency/Modular/Builder.hs
index 543a006961..de070a6297 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/Builder.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/Builder.hs
@@ -58,6 +58,8 @@ extendOpen qpn' gs s@(BS { rdeps = gs', open = o' }) = go gs' o' gs
       | qpn `M.member` g  = go (M.adjust ((c, qpn'):) qpn g)              o  ngs
       | otherwise         = go (M.insert qpn [(c, qpn')]  g) (cons' ng () o) ngs
           -- code above is correct; insert/adjust have different arg order
+    go g o (   (OpenGoal (Simple (Ext _ext ) _) _gr) : ngs) = go g o ngs
+    go g o (   (OpenGoal (Simple (Lang _lang)_) _gr) : ngs) = go g o ngs
 
     cons' = cons . forgetCompOpenGoal
 
@@ -114,6 +116,10 @@ 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 { index = _  , next = OneGoal (OpenGoal (Simple (Ext _             ) _) _ ) }) =
+      error "Distribution.Client.Dependency.Modular.Builder: build.go called with Ext goal"
+    go    (BS { index = _  , next = OneGoal (OpenGoal (Simple (Lang _            ) _) _ ) }) =
+      error "Distribution.Client.Dependency.Modular.Builder: build.go called with Lang goal"
     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)
diff --git a/cabal-install/Distribution/Client/Dependency/Modular/Dependency.hs b/cabal-install/Distribution/Client/Dependency/Modular/Dependency.hs
index 4403f5b8c1..b62ee98ee7 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/Dependency.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/Dependency.hs
@@ -59,6 +59,10 @@ import Data.Set (Set)
 import qualified Data.List as L
 import qualified Data.Set  as S
 
+import Language.Haskell.Extension (Extension(..), Language(..))
+
+import Distribution.Text
+
 import Distribution.Client.Dependency.Modular.Flag
 import Distribution.Client.Dependency.Modular.Package
 import Distribution.Client.Dependency.Modular.Version
@@ -201,7 +205,9 @@ type FalseFlaggedDeps qpn = FlaggedDeps Component qpn
 
 -- | A dependency (constraint) associates a package name with a
 -- constrained instance.
-data Dep qpn = Dep qpn (CI qpn)
+data Dep qpn = Dep  qpn (CI qpn)  -- dependency on a package
+             | Ext  Extension     -- dependency on a language extension
+             | Lang Language      -- dependency on a language version
   deriving (Eq, Show, Functor)
 
 showDep :: Dep QPN -> String
@@ -212,6 +218,8 @@ showDep (Dep qpn (Constrained [(vr, Goal v _)])) =
   showVar v ++ " => " ++ showQPN qpn ++ showVR vr
 showDep (Dep qpn ci                            ) =
   showQPN qpn ++ showCI ci
+showDep (Ext ext)   = "requires " ++ display ext
+showDep (Lang lang) = "requires " ++ display lang
 
 -- | Options for goal qualification (used in 'qualifyDeps')
 --
@@ -253,6 +261,8 @@ qualifyDeps QO{..} (Q pp' pn) = go
     -- Should we qualify this goal with the 'Base' package path?
     qBase :: Dep PN -> Bool
     qBase (Dep dep _ci) = qoBaseShim && unPackageName dep == "base"
+    qBase (Ext _)       = False
+    qBase (Lang _)      = False
 
     -- Should we qualify this goal with the 'Setup' packaeg path?
     qSetup :: Component -> Bool
@@ -381,6 +391,8 @@ instance ResetGoal CI where
 
 instance ResetGoal Dep where
   resetGoal g (Dep qpn ci) = Dep qpn (resetGoal g ci)
+  resetGoal _ (Ext ext)    = Ext ext
+  resetGoal _ (Lang lang)  = Lang lang
 
 instance ResetGoal Goal where
   resetGoal = const
@@ -415,6 +427,10 @@ data OpenGoal comp = OpenGoal (FlaggedDep comp QPN) QGoalReasonChain
 -- need only during the build phase.
 close :: OpenGoal comp -> Goal QPN
 close (OpenGoal (Simple (Dep qpn _) _) gr) = Goal (P qpn) gr
+close (OpenGoal (Simple (Ext     _) _) _ ) =
+  error "Distribution.Client.Dependency.Modular.Dependency.close: called on Ext goal"
+close (OpenGoal (Simple (Lang    _) _) _ ) =
+  error "Distribution.Client.Dependency.Modular.Dependency.close: called on Lang goal"
 close (OpenGoal (Flagged qfn _ _ _ )   gr) = Goal (F qfn) gr
 close (OpenGoal (Stanza  qsn _)        gr) = Goal (S qsn) gr
 
diff --git a/cabal-install/Distribution/Client/Dependency/Modular/IndexConversion.hs b/cabal-install/Distribution/Client/Dependency/Modular/IndexConversion.hs
index fcb06896d9..5e39c99623 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/IndexConversion.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/IndexConversion.hs
@@ -120,13 +120,13 @@ convGPD os arch comp strfl pi
     conv = convCondTree os arch comp pi fds (const True)
   in
     PInfo
-      (maybe []    (conv ComponentLib                       ) libs    ++
+      (maybe []    (conv ComponentLib                     libBuildInfo         ) libs    ++
        maybe []    (convSetupBuildInfo pi)    (setupBuildInfo pkg)    ++
-       concatMap   (\(nm, ds) -> conv (ComponentExe nm)   ds) exes    ++
+       concatMap   (\(nm, ds) -> conv (ComponentExe nm)   buildInfo          ds) exes    ++
       prefix (Stanza (SN pi TestStanzas))
-        (L.map     (\(nm, ds) -> conv (ComponentTest nm)  ds) tests)  ++
+        (L.map     (\(nm, ds) -> conv (ComponentTest nm)  testBuildInfo      ds) tests)  ++
       prefix (Stanza (SN pi BenchStanzas))
-        (L.map     (\(nm, ds) -> conv (ComponentBench nm) ds) benchs))
+        (L.map     (\(nm, ds) -> conv (ComponentBench nm) benchmarkBuildInfo ds) benchs))
       fds
       Nothing
 
@@ -143,11 +143,16 @@ flagInfo strfl = M.fromList . L.map (\ (MkFlag fn _ b m) -> (fn, FInfo b m (not
 convCondTree :: OS -> Arch -> CompilerInfo -> PI PN -> FlagInfo ->
                 (a -> Bool) -> -- how to detect if a branch is active
                 Component ->
+                (a -> BuildInfo) ->
                 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
+convCondTree os arch cinfo pi@(PI pn _) fds p comp getInfo (CondNode info ds branches)
+  | p info    =  L.map (\d -> D.Simple (convDep pn d) comp) ds  -- unconditional package dependencies
+              ++ L.map (\e -> D.Simple (Ext  e) comp) (PD.allExtensions bi) -- unconditional extension dependencies
+              ++ L.map (\l -> D.Simple (Lang l) comp) (PD.allLanguages  bi) -- unconditional language dependencies
+              ++ concatMap (convBranch os arch cinfo pi fds p comp getInfo) branches
   | otherwise = []
+  where
+    bi = getInfo info
 
 -- | Branch interpreter.
 --
@@ -161,12 +166,13 @@ convBranch :: OS -> Arch -> CompilerInfo ->
               PI PN -> FlagInfo ->
               (a -> Bool) -> -- how to detect if a branch is active
               Component ->
+              (a -> BuildInfo) ->
               (Condition ConfVar,
                CondTree ConfVar [Dependency] a,
                Maybe (CondTree ConfVar [Dependency] a)) -> FlaggedDeps Component PN
-convBranch os arch cinfo pi@(PI pn _) 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')
+convBranch os arch cinfo pi@(PI pn _) fds p comp getInfo (c', t', mf') =
+  go c' (          convCondTree os arch cinfo pi fds p comp getInfo   t')
+        (maybe [] (convCondTree os arch cinfo pi fds p comp getInfo) mf')
   where
     go :: Condition ConfVar ->
           FlaggedDeps Component PN -> FlaggedDeps Component PN -> FlaggedDeps Component PN
diff --git a/cabal-install/Distribution/Client/Dependency/Modular/Linking.hs b/cabal-install/Distribution/Client/Dependency/Modular/Linking.hs
index 26bf12235d..0b0a5ef603 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/Linking.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/Linking.hs
@@ -274,6 +274,10 @@ linkDeps parents pp' = mapM_ go
           lg'  = M.findWithDefault (lgSingleton qpn' Nothing) qpn' $ vsLinks vs
       lg'' <- lift' $ lgMerge parents lg lg'
       updateLinkGroup lg''
+    -- For extensions and language dependencies, there is nothing to do.
+    -- No choice is involved, just checking, so there is nothing to link.
+    go (Simple (Ext  _)             _) = return ()
+    go (Simple (Lang _)             _) = return ()
     go (Flagged fn _ t f) = do
       vs <- get
       case M.lookup fn (vsFlags vs) of
diff --git a/cabal-install/Distribution/Client/Dependency/Modular/Solver.hs b/cabal-install/Distribution/Client/Dependency/Modular/Solver.hs
index af5901f628..bf7feeaacb 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/Solver.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/Solver.hs
@@ -2,6 +2,8 @@ module Distribution.Client.Dependency.Modular.Solver where
 
 import Data.Map as M
 
+import Distribution.Compiler (CompilerInfo)
+
 import Distribution.Client.Dependency.Types
 
 import Distribution.Client.Dependency.Modular.Assignment
@@ -26,13 +28,14 @@ data SolverConfig = SolverConfig {
   maxBackjumps          :: Maybe Int
 }
 
-solve :: SolverConfig ->          -- solver parameters
-         Index ->                 -- all available packages as an index
+solve :: SolverConfig ->                      -- solver parameters
+         CompilerInfo ->
+         Index ->                             -- all available packages as an index
          (PN -> PackagePreferences) ->        -- preferences
          Map PN [LabeledPackageConstraint] -> -- global constraints
          [PN] ->                              -- global goals
          Log Message (Assignment, RevDepMap)
-solve sc idx userPrefs userConstraints userGoals =
+solve sc cinfo idx userPrefs userConstraints userGoals =
   explorePhase     $
   heuristicsPhase  $
   preferencesPhase $
@@ -54,7 +57,7 @@ solve sc idx userPrefs userConstraints userGoals =
                        P.enforcePackageConstraints userConstraints .
                        P.enforceSingleInstanceRestriction .
                        validateLinking idx .
-                       validateTree idx
+                       validateTree cinfo idx
     prunePhase       = (if avoidReinstalls sc then P.avoidReinstalls (const True) else id) .
                        -- packages that can never be "upgraded":
                        P.requireInstalled (`elem` [ PackageName "base"
diff --git a/cabal-install/Distribution/Client/Dependency/Modular/Validate.hs b/cabal-install/Distribution/Client/Dependency/Modular/Validate.hs
index dfd1d29be2..5b6d3c2b2a 100644
--- a/cabal-install/Distribution/Client/Dependency/Modular/Validate.hs
+++ b/cabal-install/Distribution/Client/Dependency/Modular/Validate.hs
@@ -10,9 +10,14 @@ import Control.Applicative
 import Control.Monad.Reader hiding (sequence)
 import Data.List as L
 import Data.Map as M
+import Data.Set as S
 import Data.Traversable
 import Prelude hiding (sequence)
 
+import Language.Haskell.Extension (Extension, Language)
+
+import Distribution.Compiler (CompilerInfo(..))
+
 import Distribution.Client.Dependency.Modular.Assignment
 import Distribution.Client.Dependency.Modular.Dependency
 import Distribution.Client.Dependency.Modular.Flag
@@ -75,6 +80,8 @@ import Distribution.Client.ComponentDeps (Component)
 
 -- | The state needed during validation.
 data ValidateState = VS {
+  supportedExt  :: Extension -> Bool,
+  supportedLang :: Language  -> Bool,
   index :: Index,
   saved :: Map QPN (FlaggedDeps Component QPN), -- saved, scoped, dependencies
   pa    :: PreAssignment,
@@ -123,6 +130,8 @@ validate = cata go
     goP :: QPN -> QGoalReasonChain -> POption -> Validate (Tree QGoalReasonChain) -> Validate (Tree QGoalReasonChain)
     goP qpn@(Q _pp pn) gr (POption i _) r = do
       PA ppa pfa psa <- asks pa    -- obtain current preassignment
+      extSupported   <- asks supportedExt  -- obtain the supported extensions
+      langSupported  <- asks supportedLang -- obtain the supported languages
       idx            <- asks index -- obtain the index
       svd            <- asks saved -- obtain saved dependencies
       qo             <- asks qualifyOptions
@@ -135,7 +144,7 @@ validate = cata go
       let goal = Goal (P qpn) gr
       let newactives = Dep qpn (Fixed i goal) : L.map (resetGoal goal) (extractDeps pfa psa qdeps)
       -- We now try to extend the partial assignment with the new active constraints.
-      let mnppa = extend (P qpn) ppa newactives
+      let mnppa = extend extSupported langSupported goal ppa newactives
       -- In case we continue, we save the scoped dependencies
       let nsvd = M.insert qpn qdeps svd
       case mfr of
@@ -151,6 +160,8 @@ validate = cata go
     goF :: QFN -> QGoalReasonChain -> Bool -> Validate (Tree QGoalReasonChain) -> Validate (Tree QGoalReasonChain)
     goF qfn@(FN (PI qpn _i) _f) gr b r = do
       PA ppa pfa psa <- asks pa -- obtain current preassignment
+      extSupported   <- asks supportedExt  -- obtain the supported extensions
+      langSupported  <- asks supportedLang -- obtain the supported languages
       svd <- asks saved         -- obtain saved dependencies
       -- Note that there should be saved dependencies for the package in question,
       -- because while building, we do not choose flags before we see the packages
@@ -165,7 +176,7 @@ validate = cata go
       -- we have chosen a new flag.
       let newactives = extractNewDeps (F qfn) gr b npfa psa qdeps
       -- As in the package case, we try to extend the partial assignment.
-      case extend (F qfn) ppa newactives of
+      case extend extSupported langSupported (Goal (F qfn) gr) ppa newactives of
         Left (c, d) -> return (Fail c (Conflicting d)) -- inconsistency found
         Right nppa  -> local (\ s -> s { pa = PA nppa npfa psa }) r
 
@@ -173,6 +184,8 @@ validate = cata go
     goS :: QSN -> QGoalReasonChain -> Bool -> Validate (Tree QGoalReasonChain) -> Validate (Tree QGoalReasonChain)
     goS qsn@(SN (PI qpn _i) _f) gr b r = do
       PA ppa pfa psa <- asks pa -- obtain current preassignment
+      extSupported   <- asks supportedExt  -- obtain the supported extensions
+      langSupported  <- asks supportedLang -- obtain the supported languages
       svd <- asks saved         -- obtain saved dependencies
       -- Note that there should be saved dependencies for the package in question,
       -- because while building, we do not choose flags before we see the packages
@@ -187,7 +200,7 @@ validate = cata go
       -- we have chosen a new flag.
       let newactives = extractNewDeps (S qsn) gr b pfa npsa qdeps
       -- As in the package case, we try to extend the partial assignment.
-      case extend (S qsn) ppa newactives of
+      case extend extSupported langSupported (Goal (S qsn) gr) ppa newactives of
         Left (c, d) -> return (Fail c (Conflicting d)) -- inconsistency found
         Right nppa  -> local (\ s -> s { pa = PA nppa pfa npsa }) r
 
@@ -235,10 +248,16 @@ extractNewDeps v gr b fa sa = go
                                   Just False -> []
 
 -- | Interface.
-validateTree :: Index -> Tree QGoalReasonChain -> Tree QGoalReasonChain
-validateTree idx t = runReader (validate t) VS {
-    index = idx
-  , saved = M.empty
-  , pa    = PA M.empty M.empty M.empty
+validateTree :: CompilerInfo -> Index -> Tree QGoalReasonChain -> Tree QGoalReasonChain
+validateTree cinfo idx t = runReader (validate t) VS {
+    supportedExt   = maybe (const True) -- if compiler has no list of extensions, we assume everything is supported
+                           (\ es -> let s = S.fromList es in \ x -> S.member x s)
+                           (compilerInfoExtensions cinfo)
+  , supportedLang  = maybe (const True)
+                           (flip L.elem) -- use list lookup because language list is small and no Ord instance
+                           (compilerInfoLanguages  cinfo)
+  , index          = idx
+  , saved          = M.empty
+  , pa             = PA M.empty M.empty M.empty
   , qualifyOptions = defaultQualifyOptions idx
   }
-- 
GitLab