pubgrub-cabal-0.1.0.0: src/PubGrub/Cabal/InstanceSet.hs
-- | A package's instances, and sets of them.
--
-- An instance is one concrete way of having a package: a specific pre-installed
-- unit, or a source release. Flags are not part of an instance: an automatic
-- flag is a solver variable of its own ('PubGrub.Cabal.FlagVar'), decided
-- beside the version, and a manual flag is fixed before the solver sees the
-- release.
--
-- A set of instances has two halves, represented differently because they
-- differ in kind. The installed half is /extensional/: the installed database is
-- small and fully known, so it is a plain 'Set' of units. The source half is
-- /intensional/: a dependency's declared range admits versions the index may not
-- even have, so it is kept as one 'VersionSet', a range over versions rather
-- than a list of them, and never enumerated. Both halves are canonical, so the
-- set has a lawful 'Eq' and 'Ord', every operation is exact, and 'isEmpty'
-- answers both ways.
module PubGrub.Cabal.InstanceSet
( HasVersion (..)
, Instance (..)
, InstanceSet (..)
, emptyInstanceSet
, sourceInstances
, singletonInstance
, memberInstance
, isEveryVersion
) where
import Data.Set (Set)
import qualified Data.Set as Set
import qualified Data.Text as T
import PubGrub.Cabal.PreInstalled (PreInstalledPackage (..))
import PubGrub.Cabal.Pretty ()
import PubGrub.Cabal.Version (Version)
import PubGrub.Cabal.VersionSet (VersionSet)
import qualified PubGrub.Cabal.VersionSet as VersionSet
import PubGrub.Pretty (Pretty (..))
import PubGrub.SetOps (SetOps (..))
-- | A pre-installed unit carries a version, and so does an 'Instance' — this is
-- what lets the 'PubGrub.Provider.Provider' turn a @.cabal@ dependency's
-- 'VersionSet' into the corresponding set of instances.
class HasVersion a where
version :: a -> Version
instance HasVersion PreInstalledPackage where
version = pipVersion
-- | One concrete way of having a package. It /identifies/ that way and does not
-- describe it: it carries no dependencies, which come from the provider that
-- owns the index the identifier points into.
data Instance
= UsePreInstalled PreInstalledPackage
| BuildSource Version
deriving (Eq, Ord, Show)
instance HasVersion Instance where
version (UsePreInstalled pip) = version pip
version (BuildSource v) = v
instance Pretty Instance where
pretty (UsePreInstalled pip) = T.pack "installed:" <> pretty (pipUnitId pip)
pretty (BuildSource v) = pretty v
-- | A set of 'Instance's of one package.
data InstanceSet = InstanceSet
{ isInstalled :: Set PreInstalledPackage
-- ^ the pre-installed units in the set, extensionally
, isSource :: VersionSet
-- ^ the source releases in the set, as a range
}
deriving (Eq, Ord, Show)
-- | Under union.
instance Semigroup InstanceSet where
(<>) = union
instance SetOps InstanceSet where
empty = emptyInstanceSet
union a b = InstanceSet (isInstalled a `Set.union` isInstalled b) (isSource a `union` isSource b)
intersect a b = InstanceSet (isInstalled a `Set.intersection` isInstalled b) (isSource a `intersect` isSource b)
difference a b = InstanceSet (isInstalled a `Set.difference` isInstalled b) (isSource a `difference` isSource b)
isEmpty a = Set.null (isInstalled a) && isEmpty (isSource a)
isSubsetOf a b = isInstalled a `Set.isSubsetOf` isInstalled b && isSource a `isSubsetOf` isSource b
isDisjoint a b = isInstalled a `Set.disjoint` isInstalled b && isSource a `isDisjoint` isSource b
-- | The empty instance set. @Negative emptyInstanceSet@ is the always-true
-- term ⊤; @Positive emptyInstanceSet@ is the never-true term ⊥.
emptyInstanceSet :: InstanceSet
emptyInstanceSet = InstanceSet Set.empty VersionSet.emptyVersionSet
-- | The source releases in a range, and no installed unit.
sourceInstances :: VersionSet -> InstanceSet
sourceInstances = InstanceSet Set.empty
-- | The set containing exactly one instance — the shape of a decision.
singletonInstance :: Instance -> InstanceSet
singletonInstance (UsePreInstalled pip) = InstanceSet (Set.singleton pip) VersionSet.emptyVersionSet
singletonInstance (BuildSource v) = sourceInstances (VersionSet.singletonVersionSet v)
-- | Is the instance in the set?
memberInstance :: Instance -> InstanceSet -> Bool
memberInstance (UsePreInstalled pip) s = Set.member pip (isInstalled s)
memberInstance (BuildSource v) s = VersionSet.contains (isSource s) v
-- | Does the set admit every source release of its package? True of an
-- unconstrained target's request and of a depender that spans the whole
-- version list. The report writes such a set as \"every version of foo\"
-- rather than \"foo any\".
isEveryVersion :: InstanceSet -> Bool
isEveryVersion s = isSource s == VersionSet.fullVersionSet
instance Pretty InstanceSet where
pretty s = T.intercalate (T.pack " or ") (installed <> source)
where
-- An installed unit whose version the range admits is one of the range's
-- versions, and naming it separately tells a reader nothing. The unit id
-- is never shown: it is a hash, and the version is what a reader can act
-- on.
installed =
[ pretty (pipVersion pip) <> T.pack " (installed)"
| pip <- Set.toList (isInstalled s)
, not (VersionSet.contains (isSource s) (pipVersion pip))
]
source = [pretty (isSource s) | not (isEmpty (isSource s))]