From 1347791c7bf70b1bf54edc30acbea93bad5d46d0 Mon Sep 17 00:00:00 2001
From: Oleg Grenrus <oleg.grenrus@iki.fi>
Date: Thu, 3 Sep 2020 15:39:15 +0300
Subject: [PATCH] Rewrite VersionInterval code

- The old code is preserved for now in VersionInterval.Legacy module
- Add normaliseVersionRange bench, comparing new and old
- We drop intersectVersionIntervals, which is complicated function for acceptable performance loss, but gained simplicity
- Remove Ord instances (can be added back, removed to make sure we don't use them)
---
 .gitignore                                    |   3 +
 .../src/Test/QuickCheck/Instances/Cabal.hs    |  29 +-
 Cabal/Cabal.cabal                             |   1 +
 .../src/Distribution/FieldGrammar/Newtypes.hs |   7 +-
 .../Distribution/PackageDescription/Check.hs  |   5 +-
 .../Types/PkgconfigVersionRange.hs            |   4 +-
 .../src/Distribution/Types/VersionInterval.hs | 517 +++++++++---------
 .../Types/VersionInterval/Legacy.hs           | 368 +++++++++++++
 Cabal/src/Distribution/Types/VersionRange.hs  |   4 +-
 Cabal/src/Distribution/Version.hs             |  71 +--
 Cabal/tests/UnitTests/Distribution/Version.hs | 371 +++----------
 cabal-benchmarks/bench/CabalBenchmarks.hs     | 103 +++-
 cabal-benchmarks/cabal-benchmarks.cabal       |   2 +-
 .../src/Distribution/Client/Dependency.hs     |  16 +-
 .../src/Distribution/Client/GenBounds.hs      |  19 +-
 .../src/Distribution/Client/Outdated.hs       |   4 +-
 16 files changed, 854 insertions(+), 670 deletions(-)
 create mode 100644 Cabal/src/Distribution/Types/VersionInterval/Legacy.hs

diff --git a/.gitignore b/.gitignore
index d40cd06fdf..b5bea88e87 100644
--- a/.gitignore
+++ b/.gitignore
@@ -66,3 +66,6 @@ register.sh
 
 # macOS folder metadata
 .DS_Store
+
+# benchmarks
+bench.html
diff --git a/Cabal/Cabal-QuickCheck/src/Test/QuickCheck/Instances/Cabal.hs b/Cabal/Cabal-QuickCheck/src/Test/QuickCheck/Instances/Cabal.hs
index 46299b8eda..01fd765c72 100644
--- a/Cabal/Cabal-QuickCheck/src/Test/QuickCheck/Instances/Cabal.hs
+++ b/Cabal/Cabal-QuickCheck/src/Test/QuickCheck/Instances/Cabal.hs
@@ -149,35 +149,8 @@ instance Arbitrary VersionRange where
   shrink (UnionVersionRanges a b)     = a : b : map (uncurry UnionVersionRanges) (shrink (a, b))
   shrink (IntersectVersionRanges a b) = a : b : map (uncurry IntersectVersionRanges) (shrink (a, b))
 
--- | Generating VersionIntervals
---
--- This is a tad tricky as VersionIntervals is an abstract type, so we first
--- make a local type for generating the internal representation. Then we check
--- that this lets us construct valid 'VersionIntervals'.
---
-
 instance Arbitrary VersionIntervals where
-  arbitrary = fmap mkVersionIntervals' arbitrary
-    where
-      mkVersionIntervals' :: [(Version, Bound)] -> VersionIntervals
-      mkVersionIntervals' = mkVersionIntervals . go version0
-        where
-          go :: Version -> [(Version, Bound)] -> [VersionInterval]
-          go _ [] = []
-          go v [(lv, lb)] =
-              [(LowerBound (addVersion lv v) lb, NoUpperBound)]
-          go v ((lv, lb) : (uv, ub) : rest) =
-              (LowerBound lv' lb, UpperBound uv' ub) : go uv' rest
-            where
-              lv' = addVersion v lv
-              uv' = addVersion lv' uv
-
-          addVersion :: Version -> Version -> Version
-          addVersion xs ys = mkVersion $  z (versionNumbers xs) (versionNumbers ys)
-            where
-              z [] ys' = ys'
-              z xs' [] = xs'
-              z (x : xs') (y : ys') = x + y : z xs' ys'
+  arbitrary = fmap toVersionIntervals arbitrary
 
 instance Arbitrary Bound where
   arbitrary = elements [ExclusiveBound, InclusiveBound]
diff --git a/Cabal/Cabal.cabal b/Cabal/Cabal.cabal
index 9e8b52884b..33d75f7770 100644
--- a/Cabal/Cabal.cabal
+++ b/Cabal/Cabal.cabal
@@ -521,6 +521,7 @@ library
     Distribution.Types.VersionRange
     Distribution.Types.VersionRange.Internal
     Distribution.Types.VersionInterval
+    Distribution.Types.VersionInterval.Legacy
     Distribution.Types.GivenComponent
     Distribution.Types.PackageVersionConstraint
     Distribution.Utils.Generic
diff --git a/Cabal/src/Distribution/FieldGrammar/Newtypes.hs b/Cabal/src/Distribution/FieldGrammar/Newtypes.hs
index d0b9fc297d..3f37b43eb3 100644
--- a/Cabal/src/Distribution/FieldGrammar/Newtypes.hs
+++ b/Cabal/src/Distribution/FieldGrammar/Newtypes.hs
@@ -47,7 +47,8 @@ import Distribution.License          (License)
 import Distribution.Parsec
 import Distribution.Pretty
 import Distribution.Version
-       (LowerBound (..), Version, VersionRange, VersionRangeF (..), anyVersion, asVersionIntervals, cataVersionRange, mkVersion, version0, versionNumbers)
+       (LowerBound (..), Version, VersionInterval (..), VersionRange, VersionRangeF (..), anyVersion, asVersionIntervals, cataVersionRange, mkVersion,
+       version0, versionNumbers)
 import Text.PrettyPrint              (Doc, comma, fsep, punctuate, text, vcat)
 
 import qualified Data.List.NonEmpty              as NE
@@ -332,8 +333,8 @@ instance Parsec SpecVersion where
 
         specVersionFromRange :: VersionRange -> Version
         specVersionFromRange versionRange = case asVersionIntervals versionRange of
-            []                            -> version0
-            ((LowerBound version _, _):_) -> version
+            []                                           -> version0
+            VersionInterval (LowerBound version _) _ : _ -> version
 
         simpleSpecVersionRangeSyntax = cataVersionRange alg where
             alg (OrLaterVersionF _) = True
diff --git a/Cabal/src/Distribution/PackageDescription/Check.hs b/Cabal/src/Distribution/PackageDescription/Check.hs
index cdf9040427..bbc486950e 100644
--- a/Cabal/src/Distribution/PackageDescription/Check.hs
+++ b/Cabal/src/Distribution/PackageDescription/Check.hs
@@ -1413,12 +1413,13 @@ checkPackageVersions pkg =
       -- then we will just skip the check, since boundedAbove noVersion = True
       _          -> noVersion
 
+    -- TODO: move to Distribution.Version
     boundedAbove :: VersionRange -> Bool
     boundedAbove vr = case asVersionIntervals vr of
       []     -> True -- this is the inconsistent version range.
       (x:xs) -> case last (x:|xs) of
-        (_,   UpperBound _ _) -> True
-        (_, NoUpperBound    ) -> False
+        VersionInterval _ UpperBound {} -> True
+        VersionInterval _ NoUpperBound  -> False
 
 
 checkConditionals :: GenericPackageDescription -> [PackageCheck]
diff --git a/Cabal/src/Distribution/Types/PkgconfigVersionRange.hs b/Cabal/src/Distribution/Types/PkgconfigVersionRange.hs
index 6c13c47953..2d0690b15e 100644
--- a/Cabal/src/Distribution/Types/PkgconfigVersionRange.hs
+++ b/Cabal/src/Distribution/Types/PkgconfigVersionRange.hs
@@ -151,8 +151,8 @@ versionRangeToPkgconfigVersionRange vr
         []     -> PcEarlierVersion (PkgconfigVersion (BS8.pack "0"))
         (i:is) -> foldl (\r j -> PcUnionVersionRanges r (conv j)) (conv i) is
   where
-    conv (LowerBound v b, NoUpperBound)   = convL v b
-    conv (LowerBound v b, UpperBound u c) = PcIntersectVersionRanges (convL v b) (convU u c)
+    conv (VersionInterval (LowerBound v b) NoUpperBound)     = convL v b
+    conv (VersionInterval (LowerBound v b) (UpperBound u c)) = PcIntersectVersionRanges (convL v b) (convU u c)
 
     convL v ExclusiveBound = PcLaterVersion (versionToPkgconfigVersion v)
     convL v InclusiveBound = PcOrLaterVersion (versionToPkgconfigVersion v)
diff --git a/Cabal/src/Distribution/Types/VersionInterval.hs b/Cabal/src/Distribution/Types/VersionInterval.hs
index d07a47f3fd..622faa7787 100644
--- a/Cabal/src/Distribution/Types/VersionInterval.hs
+++ b/Cabal/src/Distribution/Types/VersionInterval.hs
@@ -1,35 +1,194 @@
+{-# LANGUAGE BangPatterns       #-}
 {-# LANGUAGE DeriveDataTypeable #-}
+-- | In @Cabal-3.6@ this module have been rewritten.
+--
+--
 module Distribution.Types.VersionInterval (
     -- * Version intervals
     VersionIntervals,
+    unVersionIntervals,
+
+    -- * Conversions
     toVersionIntervals,
     fromVersionIntervals,
-    withinIntervals,
-    versionIntervals,
-    mkVersionIntervals,
-    unionVersionIntervals,
-    intersectVersionIntervals,
-    invertVersionIntervals,
+
+    -- ** Normalisation
+    normaliseVersionRange2,
+
+    -- * Relaxation
     relaxLastInterval,
     relaxHeadInterval,
 
     -- * Version intervals view
     asVersionIntervals,
-    VersionInterval,
+    VersionInterval (..),
     LowerBound(..),
     UpperBound(..),
     Bound(..),
+
+    -- * Invariants
+    invariantVersionIntervals,
     ) where
 
-import Prelude ()
+import Control.Applicative         (liftA2)
+import Control.Exception           (assert)
 import Distribution.Compat.Prelude
-import Control.Exception (assert)
+import Prelude ()
 
 import Distribution.Types.Version
 import Distribution.Types.VersionRange.Internal
 
--- NonEmpty
-import qualified Prelude (foldr1)
+-- To test this module, and to run version range normalisation benchmarks:
+--
+-- cabal run Cabal:unit-tests -- -p Distribution.Version
+-- cabal run cabal-benchmarks -- -o bench.html normaliseVersionRange
+
+-------------------------------------------------------------------------------
+-- Data
+-------------------------------------------------------------------------------
+
+-- | A complementary representation of a 'VersionRange'. Instead of a boolean
+-- version predicate it uses an increasing sequence of non-overlapping,
+-- non-empty intervals.
+--
+-- The key point is that this representation gives a canonical representation
+-- for the semantics of 'VersionRange's. This makes it easier to check things
+-- like whether a version range is empty, covers all versions, or requires a
+-- certain minimum or maximum version. It also makes it easy to check equality
+-- or containment. It also makes it easier to identify \'simple\' version
+-- predicates for translation into foreign packaging systems that do not
+-- support complex version range expressions.
+--
+newtype VersionIntervals = VersionIntervals [VersionInterval]
+  deriving (Eq, Show, Typeable)
+
+-- | Inspect the list of version intervals.
+--
+unVersionIntervals :: VersionIntervals -> [VersionInterval]
+unVersionIntervals (VersionIntervals is) = is
+
+data VersionInterval = VersionInterval !LowerBound !UpperBound    deriving (Eq, Show)
+data LowerBound      = LowerBound !Version !Bound                 deriving (Eq, Show)
+data UpperBound      = NoUpperBound | UpperBound !Version !Bound  deriving (Eq, Show)
+data Bound           = ExclusiveBound | InclusiveBound            deriving (Eq, Show)
+
+zeroLowerBound :: LowerBound
+zeroLowerBound = LowerBound version0 InclusiveBound
+
+isVersion0 :: Version -> Bool
+isVersion0 = (==) version0
+
+-------------------------------------------------------------------------------
+-- Stage1
+-------------------------------------------------------------------------------
+
+stage1 :: VersionRange -> [VersionInterval]
+stage1 = cataVersionRange alg where
+    -- version range leafs transform into singleton intervals
+    alg (ThisVersionF v)                = [VersionInterval (LowerBound v InclusiveBound) (UpperBound v InclusiveBound)]
+    alg (LaterVersionF v)               = [VersionInterval (LowerBound v ExclusiveBound) NoUpperBound]
+    alg (OrLaterVersionF v)             = [VersionInterval (LowerBound v InclusiveBound) NoUpperBound]
+    alg (EarlierVersionF v)
+        | isVersion0 v                  = []
+        | otherwise                     = [VersionInterval zeroLowerBound                (UpperBound v ExclusiveBound)]
+    alg (OrEarlierVersionF v)           = [VersionInterval zeroLowerBound                (UpperBound v InclusiveBound)]
+
+    -- ^>= version-range's upper bound should be MajorBound
+    alg (MajorBoundVersionF v)          = [VersionInterval (LowerBound v InclusiveBound) (UpperBound (majorUpperBound v) ExclusiveBound)]
+
+    -- union: just merge the version intervals
+    alg (UnionVersionRangesF v1 v2)     = v1 ++ v2
+
+    -- intersection: pairwise intersect. Strip empty intervals. Sort to restore the invariant.
+    alg (IntersectVersionRangesF v1 v2) = mapMaybe nonEmptyInterval $ liftA2 intersectInterval (stage2and3 v1) (stage2and3 v2)
+
+-- | Creck that interval is non-empty
+nonEmptyInterval :: VersionInterval -> Maybe VersionInterval
+nonEmptyInterval i | nonEmptyVI i = Just i
+nonEmptyInterval _                = Nothing
+
+-------------------------------------------------------------------------------
+-- Stage2
+-------------------------------------------------------------------------------
+
+stage2 :: [VersionInterval] -> [VersionInterval]
+stage2 = sortBy lowerboundCmp
+
+lowerboundCmp :: VersionInterval -> VersionInterval -> Ordering
+lowerboundCmp (VersionInterval (LowerBound v vb) _) (VersionInterval (LowerBound u ub) _) =
+    compare v u `mappend` compareBound vb ub
+  where
+    compareBound :: Bound -> Bound -> Ordering
+    compareBound InclusiveBound InclusiveBound = EQ
+    compareBound InclusiveBound ExclusiveBound = LT
+    compareBound ExclusiveBound InclusiveBound = GT
+    compareBound ExclusiveBound ExclusiveBound = EQ
+
+-------------------------------------------------------------------------------
+-- Postprocess
+-------------------------------------------------------------------------------
+
+-- | Post-processing takes a list of ordered version intervals,
+-- but possibly overlapping, and creates 'VersionIntervals'.
+--
+postprocess :: [VersionInterval] -> VersionIntervals
+postprocess = checkInvariant . VersionIntervals . stage2and3
+
+stage2and3 :: [VersionInterval] -> [VersionInterval]
+stage2and3 = stage3 . stage2
+
+stage3 :: [VersionInterval] -> [VersionInterval]
+stage3 []                                     = []
+stage3 (VersionInterval lb ub : rest)         = stage3go lb ub rest
+
+stage3go :: LowerBound -> UpperBound -> [VersionInterval] -> [VersionInterval]
+stage3go !lb NoUpperBound _                                 = [VersionInterval lb NoUpperBound]
+stage3go !lb !ub          []                                = [VersionInterval lb ub]
+stage3go !lb !ub          (VersionInterval lb' ub' : rest')
+    | doesNotTouch ub lb'                                   = VersionInterval lb ub : stage3go lb' ub' rest'
+    | otherwise                                             = stage3go lb (unionUpper ub ub') rest'
+
+-------------------------------------------------------------------------------
+-- Intersections
+-------------------------------------------------------------------------------
+
+intersectInterval :: VersionInterval -> VersionInterval -> VersionInterval
+intersectInterval (VersionInterval lv uv) (VersionInterval lu uu) =
+    VersionInterval (intersectLower lv lu) (intersectUpper uv uu)
+
+intersectLower :: LowerBound -> LowerBound -> LowerBound
+intersectLower (LowerBound v vb) (LowerBound u ub) = case compare v u of
+    EQ -> LowerBound v (intersectBound vb ub)
+    LT -> LowerBound u ub
+    GT -> LowerBound v vb
+
+intersectUpper :: UpperBound -> UpperBound -> UpperBound
+intersectUpper NoUpperBound      b                 = b
+intersectUpper b                 NoUpperBound      = b
+intersectUpper (UpperBound v vb) (UpperBound u ub) = case compare v u of
+    EQ -> UpperBound v (intersectBound vb ub)
+    LT -> UpperBound v vb
+    GT -> UpperBound u ub
+
+intersectBound :: Bound -> Bound -> Bound
+intersectBound InclusiveBound InclusiveBound = InclusiveBound
+intersectBound _              _              = ExclusiveBound
+
+-------------------------------------------------------------------------------
+-- Unions
+-------------------------------------------------------------------------------
+
+unionUpper :: UpperBound -> UpperBound -> UpperBound
+unionUpper NoUpperBound      _                 = NoUpperBound
+unionUpper _                 NoUpperBound      = NoUpperBound
+unionUpper (UpperBound v vb) (UpperBound u ub) = case compare v u of
+    EQ -> UpperBound v (unionBound vb ub)
+    LT -> UpperBound u ub
+    GT -> UpperBound v vb
+
+unionBound :: Bound -> Bound -> Bound
+unionBound ExclusiveBound ExclusiveBound = ExclusiveBound
+unionBound _              _              = InclusiveBound
 
 -------------------------------------------------------------------------------
 -- VersionRange
@@ -58,64 +217,40 @@ import qualified Prelude (foldr1)
 -- >    | otherwise = Nothing
 --
 asVersionIntervals :: VersionRange -> [VersionInterval]
-asVersionIntervals = versionIntervals . toVersionIntervals
-
+asVersionIntervals = unVersionIntervals . toVersionIntervals
 
 -------------------------------------------------------------------------------
--- VersionInterval
+-- Helpers
 -------------------------------------------------------------------------------
 
--- | A complementary representation of a 'VersionRange'. Instead of a boolean
--- version predicate it uses an increasing sequence of non-overlapping,
--- non-empty intervals.
+-- | Check an upper bound does not intersect, or even touch a lower bound:
 --
--- The key point is that this representation gives a canonical representation
--- for the semantics of 'VersionRange's. This makes it easier to check things
--- like whether a version range is empty, covers all versions, or requires a
--- certain minimum or maximum version. It also makes it easy to check equality
--- or containment. It also makes it easier to identify \'simple\' version
--- predicates for translation into foreign packaging systems that do not
--- support complex version range expressions.
---
-newtype VersionIntervals = VersionIntervals [VersionInterval]
-  deriving (Eq, Show, Typeable)
-
--- | Inspect the list of version intervals.
+-- @
+--   ---|      or  ---)     but not  ---]     or  ---)     or  ---]
+--       |---         (---              (---         [---         [---
+-- @
 --
-versionIntervals :: VersionIntervals -> [VersionInterval]
-versionIntervals (VersionIntervals is) = is
-
-type VersionInterval = (LowerBound, UpperBound)
-data LowerBound =                LowerBound Version !Bound deriving (Eq, Show)
-data UpperBound = NoUpperBound | UpperBound Version !Bound deriving (Eq, Show)
-data Bound      = ExclusiveBound | InclusiveBound          deriving (Eq, Show)
-
-minLowerBound :: LowerBound
-minLowerBound = LowerBound (mkVersion [0]) InclusiveBound
+doesNotTouch :: UpperBound -> LowerBound -> Bool
+doesNotTouch NoUpperBound      _                 = False
+doesNotTouch (UpperBound u ub) (LowerBound l lb) =
+    (u < l) || (u == l && ub == ExclusiveBound && lb == ExclusiveBound)
 
-isVersion0 :: Version -> Bool
-isVersion0 = (==) version0
+-------------------------------------------------------------------------------
+-- Invariants
+-------------------------------------------------------------------------------
 
-instance Ord LowerBound where
-  LowerBound ver bound <= LowerBound ver' bound' = case compare ver ver' of
-    LT -> True
-    EQ -> not (bound == ExclusiveBound && bound' == InclusiveBound)
-    GT -> False
-
-instance Ord UpperBound where
-  _            <= NoUpperBound   = True
-  NoUpperBound <= UpperBound _ _ = False
-  UpperBound ver bound <= UpperBound ver' bound' = case compare ver ver' of
-    LT -> True
-    EQ -> not (bound == InclusiveBound && bound' == ExclusiveBound)
-    GT -> False
-
-invariant :: VersionIntervals -> Bool
-invariant (VersionIntervals intervals) = all validInterval intervals
-                                      && all doesNotTouch' adjacentIntervals
+-- | 'VersionIntervals' invariant:
+--
+-- * all intervals are valid (lower bound is less then upper bound, i.e. non-empty)
+-- * intervals doesn't touch each other (distinct)
+--
+invariantVersionIntervals :: VersionIntervals -> Bool
+invariantVersionIntervals (VersionIntervals intervals) =
+    all validInterval intervals &&
+    all doesNotTouch' adjacentIntervals
   where
     doesNotTouch' :: (VersionInterval, VersionInterval) -> Bool
-    doesNotTouch' ((_,u), (l',_)) = doesNotTouch u l'
+    doesNotTouch' (VersionInterval _ u, VersionInterval l' _) = doesNotTouch u l'
 
     adjacentIntervals :: [(VersionInterval, VersionInterval)]
     adjacentIntervals = case intervals of
@@ -123,242 +258,88 @@ invariant (VersionIntervals intervals) = all validInterval intervals
       (_:tl) -> zip intervals tl
 
 checkInvariant :: VersionIntervals -> VersionIntervals
-checkInvariant is = assert (invariant is) is
+checkInvariant is = assert (invariantVersionIntervals is) is
+{-# INLINE checkInvariant #-}
 
--- | Directly construct a 'VersionIntervals' from a list of intervals.
---
--- In @Cabal-2.2@ the 'Maybe' is dropped from the result type.
---
-mkVersionIntervals :: [VersionInterval] -> VersionIntervals
-mkVersionIntervals intervals
-    | invariant (VersionIntervals intervals) = VersionIntervals intervals
-    | otherwise
-        = checkInvariant
-        . foldl' (flip insertInterval) (VersionIntervals [])
-        . filter validInterval
-        $ intervals
-
-insertInterval :: VersionInterval -> VersionIntervals -> VersionIntervals
-insertInterval i is = unionVersionIntervals (VersionIntervals [i]) is
-
-validInterval :: (LowerBound, UpperBound) -> Bool
-validInterval i@(l, u) = validLower l && validUpper u && nonEmptyVI i
+validInterval :: VersionInterval -> Bool
+validInterval i@(VersionInterval l u) = validLower l && validUpper u && nonEmptyVI i
   where
     validLower (LowerBound v _) = validVersion v
-    validUpper NoUpperBound     = True
-    validUpper (UpperBound v _) = validVersion v
+    validUpper NoUpperBound      = True
+    validUpper (UpperBound v _)  = validVersion v
 
 -- Check an interval is non-empty
 --
 nonEmptyVI :: VersionInterval -> Bool
-nonEmptyVI (_,               NoUpperBound   ) = True
-nonEmptyVI (LowerBound l lb, UpperBound u ub) =
+nonEmptyVI (VersionInterval _                 NoUpperBound)      = True
+nonEmptyVI (VersionInterval (LowerBound l lb) (UpperBound u ub)) =
   (l < u) || (l == u && lb == InclusiveBound && ub == InclusiveBound)
 
--- Check an upper bound does not intersect, or even touch a lower bound:
---
---   ---|      or  ---)     but not  ---]     or  ---)     or  ---]
---       |---         (---              (---         [---         [---
---
-doesNotTouch :: UpperBound -> LowerBound -> Bool
-doesNotTouch NoUpperBound _ = False
-doesNotTouch (UpperBound u ub) (LowerBound l lb) =
-      u <  l
-  || (u == l && ub == ExclusiveBound && lb == ExclusiveBound)
-
--- | Check an upper bound does not intersect a lower bound:
---
---   ---|      or  ---)     or  ---]     or  ---)     but not  ---]
---       |---         (---         (---         [---              [---
---
-doesNotIntersect :: UpperBound -> LowerBound -> Bool
-doesNotIntersect NoUpperBound _ = False
-doesNotIntersect (UpperBound u ub) (LowerBound l lb) =
-      u <  l
-  || (u == l && not (ub == InclusiveBound && lb == InclusiveBound))
-
--- | Test if a version falls within the version intervals.
---
--- It exists mostly for completeness and testing. It satisfies the following
--- properties:
---
--- > withinIntervals v (toVersionIntervals vr) = withinRange v vr
--- > withinIntervals v ivs = withinRange v (fromVersionIntervals ivs)
---
-withinIntervals :: Version -> VersionIntervals -> Bool
-withinIntervals v (VersionIntervals intervals) = any withinInterval intervals
-  where
-    withinInterval (lowerBound, upperBound)    = withinLower lowerBound
-                                              && withinUpper upperBound
-    withinLower (LowerBound v' ExclusiveBound) = v' <  v
-    withinLower (LowerBound v' InclusiveBound) = v' <= v
-
-    withinUpper NoUpperBound                   = True
-    withinUpper (UpperBound v' ExclusiveBound) = v' >  v
-    withinUpper (UpperBound v' InclusiveBound) = v' >= v
+-------------------------------------------------------------------------------
+-- Conversions
+-------------------------------------------------------------------------------
 
 -- | Convert a 'VersionRange' to a sequence of version intervals.
 --
 toVersionIntervals :: VersionRange -> VersionIntervals
-toVersionIntervals = cataVersionRange alg where
-    alg (ThisVersionF v)                = chkIvl (LowerBound v InclusiveBound, UpperBound v InclusiveBound)
-    alg (LaterVersionF v)               = chkIvl (LowerBound v ExclusiveBound, NoUpperBound)
-    alg (OrLaterVersionF v)             = chkIvl (LowerBound v InclusiveBound, NoUpperBound)
-    alg (EarlierVersionF v)
-        | isVersion0 v                  = VersionIntervals []
-        | otherwise                     = chkIvl (minLowerBound,               UpperBound v ExclusiveBound)
-    alg (OrEarlierVersionF v)           = chkIvl (minLowerBound,               UpperBound v InclusiveBound)
-    alg (MajorBoundVersionF v)          = chkIvl (LowerBound v InclusiveBound, UpperBound (majorUpperBound v) ExclusiveBound)
-    alg (UnionVersionRangesF v1 v2)     = unionVersionIntervals v1 v2
-    alg (IntersectVersionRangesF v1 v2) = intersectVersionIntervals v1 v2
-
-    chkIvl interval = checkInvariant (VersionIntervals [interval])
+toVersionIntervals = postprocess . stage1
 
 -- | Convert a 'VersionIntervals' value back into a 'VersionRange' expression
 -- representing the version intervals.
 --
 fromVersionIntervals :: VersionIntervals -> VersionRange
-fromVersionIntervals (VersionIntervals []) = noVersion
-fromVersionIntervals (VersionIntervals intervals) =
-    Prelude.foldr1 unionVersionRanges [ interval l u | (l, u) <- intervals ]
+fromVersionIntervals (VersionIntervals [])     = noVersion
+fromVersionIntervals (VersionIntervals (x:xs)) = foldr1 unionVersionRanges (fmap intervalToVersionRange (x:|xs))
 
-  where
-    interval (LowerBound v  InclusiveBound)
-             (UpperBound v' InclusiveBound) | v == v'
-                 = thisVersion v
-    interval l u = lowerBound l `intersectVersionRanges'` upperBound u
-
-    lowerBound (LowerBound v InclusiveBound)
-                              | isVersion0 v = Nothing
-                              | otherwise    = Just (orLaterVersion v)
-    lowerBound (LowerBound v ExclusiveBound) = Just (laterVersion v)
-
-    upperBound NoUpperBound                  = Nothing
-    upperBound (UpperBound v InclusiveBound) = Just (orEarlierVersion v)
-    upperBound (UpperBound v ExclusiveBound) = Just (earlierVersion v)
-
-    intersectVersionRanges' Nothing Nothing      = anyVersion
-    intersectVersionRanges' (Just vr) Nothing    = vr
-    intersectVersionRanges' Nothing (Just vr)    = vr
-    intersectVersionRanges' (Just vr) (Just vr') = intersectVersionRanges vr vr'
-
-unionVersionIntervals :: VersionIntervals -> VersionIntervals
-                      -> VersionIntervals
-unionVersionIntervals (VersionIntervals is0) (VersionIntervals is'0) =
-  checkInvariant (VersionIntervals (union is0 is'0))
-  where
-    union is []  = is
-    union [] is' = is'
-    union (i:is) (i':is') = case unionInterval i i' of
-      Left  Nothing    -> i  : union      is  (i' :is')
-      Left  (Just i'') ->      union      is  (i'':is')
-      Right Nothing    -> i' : union (i  :is)      is'
-      Right (Just i'') ->      union (i'':is)      is'
-
-unionInterval :: VersionInterval -> VersionInterval
-              -> Either (Maybe VersionInterval) (Maybe VersionInterval)
-unionInterval (lower , upper ) (lower', upper')
-
-  -- Non-intersecting intervals with the left interval ending first
-  | upper `doesNotTouch` lower' = Left Nothing
-
-  -- Non-intersecting intervals with the right interval first
-  | upper' `doesNotTouch` lower = Right Nothing
-
-  -- Complete or partial overlap, with the left interval ending first
-  | upper <= upper' = lowerBound `seq`
-                      Left (Just (lowerBound, upper'))
-
-  -- Complete or partial overlap, with the left interval ending first
-  | otherwise = lowerBound `seq`
-                Right (Just (lowerBound, upper))
-  where
-    lowerBound = min lower lower'
+intervalToVersionRange :: VersionInterval -> VersionRange
+intervalToVersionRange (VersionInterval (LowerBound v vb) upper') = case upper' of
+    NoUpperBound
+        -> lowerBound
 
-intersectVersionIntervals :: VersionIntervals -> VersionIntervals
-                          -> VersionIntervals
-intersectVersionIntervals (VersionIntervals is0) (VersionIntervals is'0) =
-  checkInvariant (VersionIntervals (intersect is0 is'0))
-  where
-    intersect _  [] = []
-    intersect [] _  = []
-    intersect (i:is) (i':is') = case intersectInterval i i' of
-      Left  Nothing    ->       intersect is (i':is')
-      Left  (Just i'') -> i'' : intersect is (i':is')
-      Right Nothing    ->       intersect (i:is) is'
-      Right (Just i'') -> i'' : intersect (i:is) is'
-
-intersectInterval :: VersionInterval -> VersionInterval
-                  -> Either (Maybe VersionInterval) (Maybe VersionInterval)
-intersectInterval (lower , upper ) (lower', upper')
-
-  -- Non-intersecting intervals with the left interval ending first
-  | upper `doesNotIntersect` lower' = Left Nothing
-
-  -- Non-intersecting intervals with the right interval first
-  | upper' `doesNotIntersect` lower = Right Nothing
-
-  -- Complete or partial overlap, with the left interval ending first
-  | upper <= upper' = lowerBound `seq`
-                      Left (Just (lowerBound, upper))
-
-  -- Complete or partial overlap, with the right interval ending first
-  | otherwise = lowerBound `seq`
-                Right (Just (lowerBound, upper'))
+    UpperBound u ub
+        | vb == InclusiveBound
+        , ub == InclusiveBound
+        , v == u
+        -> thisVersion v
+
+    UpperBound u ub -> withLowerBound (makeUpperBound u ub)
   where
-    lowerBound = max lower lower'
-
-invertVersionIntervals :: VersionIntervals
-                       -> VersionIntervals
-invertVersionIntervals (VersionIntervals xs) =
-    case xs of
-      -- Empty interval set
-      [] -> VersionIntervals [(noLowerBound, NoUpperBound)]
-      -- Interval with no lower bound
-      ((lb, ub) : more) | lb == noLowerBound ->
-        VersionIntervals $ invertVersionIntervals' ub more
-      -- Interval with a lower bound
-      ((lb, ub) : more) ->
-          VersionIntervals $ (noLowerBound, invertLowerBound lb)
-          : invertVersionIntervals' ub more
-    where
-      -- Invert subsequent version intervals given the upper bound of
-      -- the intervals already inverted.
-      invertVersionIntervals' :: UpperBound
-                              -> [(LowerBound, UpperBound)]
-                              -> [(LowerBound, UpperBound)]
-      invertVersionIntervals' NoUpperBound [] = []
-      invertVersionIntervals' ub0 [] = [(invertUpperBound ub0, NoUpperBound)]
-      invertVersionIntervals' ub0 [(lb, NoUpperBound)] =
-          [(invertUpperBound ub0, invertLowerBound lb)]
-      invertVersionIntervals' ub0 ((lb, ub1) : more) =
-          (invertUpperBound ub0, invertLowerBound lb)
-            : invertVersionIntervals' ub1 more
-
-      invertLowerBound :: LowerBound -> UpperBound
-      invertLowerBound (LowerBound v b) = UpperBound v (invertBound b)
-
-      invertUpperBound :: UpperBound -> LowerBound
-      invertUpperBound (UpperBound v b) = LowerBound v (invertBound b)
-      invertUpperBound NoUpperBound = error "NoUpperBound: unexpected"
-
-      invertBound :: Bound -> Bound
-      invertBound ExclusiveBound = InclusiveBound
-      invertBound InclusiveBound = ExclusiveBound
-
-      noLowerBound :: LowerBound
-      noLowerBound = LowerBound (mkVersion [0]) InclusiveBound
+    lowerBound :: VersionRange
+    lowerBound = case vb of
+        InclusiveBound -> orLaterVersion v
+        ExclusiveBound -> laterVersion v
+
+    withLowerBound :: VersionRange -> VersionRange
+    withLowerBound vr
+        | isVersion0 v, vb == InclusiveBound = vr
+        | otherwise                          = intersectVersionRanges lowerBound vr
 
+    makeUpperBound :: Version -> Bound -> VersionRange
+    makeUpperBound u InclusiveBound = orEarlierVersion u
+    makeUpperBound u ExclusiveBound = earlierVersion u
+
+-------------------------------------------------------------------------------
+-- Normalisation
+-------------------------------------------------------------------------------
+
+-- | Since @Cabal-3.6@ this function.. TODO
+--
+normaliseVersionRange2 :: VersionRange -> VersionRange
+normaliseVersionRange2 = fromVersionIntervals . toVersionIntervals
+
+-------------------------------------------------------------------------------
+-- Relaxation
+-------------------------------------------------------------------------------
 
 relaxLastInterval :: VersionIntervals -> VersionIntervals
 relaxLastInterval (VersionIntervals xs) = VersionIntervals (relaxLastInterval' xs)
   where
-    relaxLastInterval' []      = []
-    relaxLastInterval' [(l,_)] = [(l, NoUpperBound)]
-    relaxLastInterval' (i:is)  = i : relaxLastInterval' is
+    relaxLastInterval' []                    = []
+    relaxLastInterval' [VersionInterval l _] = [VersionInterval l NoUpperBound]
+    relaxLastInterval' (i:is)                = i : relaxLastInterval' is
 
 relaxHeadInterval :: VersionIntervals -> VersionIntervals
 relaxHeadInterval (VersionIntervals xs) = VersionIntervals (relaxHeadInterval' xs)
   where
-    relaxHeadInterval' []         = []
-    relaxHeadInterval' ((_,u):is) = (minLowerBound,u) : is
+    relaxHeadInterval' []                         = []
+    relaxHeadInterval' (VersionInterval _ u : is) = VersionInterval zeroLowerBound u : is
diff --git a/Cabal/src/Distribution/Types/VersionInterval/Legacy.hs b/Cabal/src/Distribution/Types/VersionInterval/Legacy.hs
new file mode 100644
index 0000000000..4d16156fb7
--- /dev/null
+++ b/Cabal/src/Distribution/Types/VersionInterval/Legacy.hs
@@ -0,0 +1,368 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+-- | This is old version of "Distribution.Types.VersionInterval" module.
+--
+-- It will be removed in @Cabal-3.8@.
+--
+module Distribution.Types.VersionInterval.Legacy {-# DEPRECATED "Use Distribution.Types.VersionInterval instead" #-} (
+    -- * Version intervals
+    VersionIntervals,
+    toVersionIntervals,
+    fromVersionIntervals,
+    withinIntervals,
+    versionIntervals,
+    mkVersionIntervals,
+    unionVersionIntervals,
+    intersectVersionIntervals,
+    invertVersionIntervals,
+    relaxLastInterval,
+    relaxHeadInterval,
+
+    -- * Version intervals view
+    asVersionIntervals,
+    VersionInterval,
+    LowerBound(..),
+    UpperBound(..),
+    Bound(..),
+    ) where
+
+import Prelude ()
+import Distribution.Compat.Prelude
+import Control.Exception (assert)
+
+import Distribution.Types.Version
+import Distribution.Types.VersionRange.Internal
+
+-- NonEmpty
+import qualified Prelude (foldr1)
+
+-------------------------------------------------------------------------------
+-- VersionRange
+-------------------------------------------------------------------------------
+
+-- | View a 'VersionRange' as a union of intervals.
+--
+-- This provides a canonical view of the semantics of a 'VersionRange' as
+-- opposed to the syntax of the expression used to define it. For the syntactic
+-- view use 'foldVersionRange'.
+--
+-- Each interval is non-empty. The sequence is in increasing order and no
+-- intervals overlap or touch. Therefore only the first and last can be
+-- unbounded. The sequence can be empty if the range is empty
+-- (e.g. a range expression like @< 1 && > 2@).
+--
+-- Other checks are trivial to implement using this view. For example:
+--
+-- > isNoVersion vr | [] <- asVersionIntervals vr = True
+-- >                | otherwise                   = False
+--
+-- > isSpecificVersion vr
+-- >    | [(LowerBound v  InclusiveBound
+-- >       ,UpperBound v' InclusiveBound)] <- asVersionIntervals vr
+-- >    , v == v'   = Just v
+-- >    | otherwise = Nothing
+--
+asVersionIntervals :: VersionRange -> [VersionInterval]
+asVersionIntervals = versionIntervals . toVersionIntervals
+
+
+-------------------------------------------------------------------------------
+-- VersionInterval
+-------------------------------------------------------------------------------
+
+-- | A complementary representation of a 'VersionRange'. Instead of a boolean
+-- version predicate it uses an increasing sequence of non-overlapping,
+-- non-empty intervals.
+--
+-- The key point is that this representation gives a canonical representation
+-- for the semantics of 'VersionRange's. This makes it easier to check things
+-- like whether a version range is empty, covers all versions, or requires a
+-- certain minimum or maximum version. It also makes it easy to check equality
+-- or containment. It also makes it easier to identify \'simple\' version
+-- predicates for translation into foreign packaging systems that do not
+-- support complex version range expressions.
+--
+newtype VersionIntervals = VersionIntervals [VersionInterval]
+  deriving (Eq, Show, Typeable)
+
+-- | Inspect the list of version intervals.
+--
+versionIntervals :: VersionIntervals -> [VersionInterval]
+versionIntervals (VersionIntervals is) = is
+
+type VersionInterval = (LowerBound, UpperBound)
+data LowerBound =                LowerBound Version !Bound deriving (Eq, Show)
+data UpperBound = NoUpperBound | UpperBound Version !Bound deriving (Eq, Show)
+data Bound      = ExclusiveBound | InclusiveBound          deriving (Eq, Show)
+
+minLowerBound :: LowerBound
+minLowerBound = LowerBound (mkVersion [0]) InclusiveBound
+
+isVersion0 :: Version -> Bool
+isVersion0 = (==) version0
+
+instance Ord LowerBound where
+  LowerBound ver bound <= LowerBound ver' bound' = case compare ver ver' of
+    LT -> True
+    EQ -> not (bound == ExclusiveBound && bound' == InclusiveBound)
+    GT -> False
+
+instance Ord UpperBound where
+  _            <= NoUpperBound   = True
+  NoUpperBound <= UpperBound _ _ = False
+  UpperBound ver bound <= UpperBound ver' bound' = case compare ver ver' of
+    LT -> True
+    EQ -> not (bound == InclusiveBound && bound' == ExclusiveBound)
+    GT -> False
+
+invariant :: VersionIntervals -> Bool
+invariant (VersionIntervals intervals) = all validInterval intervals
+                                      && all doesNotTouch' adjacentIntervals
+  where
+    doesNotTouch' :: (VersionInterval, VersionInterval) -> Bool
+    doesNotTouch' ((_,u), (l',_)) = doesNotTouch u l'
+
+    adjacentIntervals :: [(VersionInterval, VersionInterval)]
+    adjacentIntervals = case intervals of
+      []     -> []
+      (_:tl) -> zip intervals tl
+
+checkInvariant :: VersionIntervals -> VersionIntervals
+checkInvariant is = assert (invariant is) is
+
+-- | Directly construct a 'VersionIntervals' from a list of intervals.
+--
+-- In @Cabal-2.2@ the 'Maybe' is dropped from the result type.
+--
+mkVersionIntervals :: [VersionInterval] -> VersionIntervals
+mkVersionIntervals intervals
+    | invariant (VersionIntervals intervals) = VersionIntervals intervals
+    | otherwise
+        = checkInvariant
+        . foldl' (flip insertInterval) (VersionIntervals [])
+        . filter validInterval
+        $ intervals
+
+insertInterval :: VersionInterval -> VersionIntervals -> VersionIntervals
+insertInterval i is = unionVersionIntervals (VersionIntervals [i]) is
+
+validInterval :: (LowerBound, UpperBound) -> Bool
+validInterval i@(l, u) = validLower l && validUpper u && nonEmptyVI i
+  where
+    validLower (LowerBound v _) = validVersion v
+    validUpper NoUpperBound     = True
+    validUpper (UpperBound v _) = validVersion v
+
+-- Check an interval is non-empty
+--
+nonEmptyVI :: VersionInterval -> Bool
+nonEmptyVI (_,               NoUpperBound   ) = True
+nonEmptyVI (LowerBound l lb, UpperBound u ub) =
+  (l < u) || (l == u && lb == InclusiveBound && ub == InclusiveBound)
+
+-- Check an upper bound does not intersect, or even touch a lower bound:
+--
+--   ---|      or  ---)     but not  ---]     or  ---)     or  ---]
+--       |---         (---              (---         [---         [---
+--
+doesNotTouch :: UpperBound -> LowerBound -> Bool
+doesNotTouch NoUpperBound _ = False
+doesNotTouch (UpperBound u ub) (LowerBound l lb) =
+      u <  l
+  || (u == l && ub == ExclusiveBound && lb == ExclusiveBound)
+
+-- | Check an upper bound does not intersect a lower bound:
+--
+--   ---|      or  ---)     or  ---]     or  ---)     but not  ---]
+--       |---         (---         (---         [---              [---
+--
+doesNotIntersect :: UpperBound -> LowerBound -> Bool
+doesNotIntersect NoUpperBound _ = False
+doesNotIntersect (UpperBound u ub) (LowerBound l lb) =
+      u <  l
+  || (u == l && not (ub == InclusiveBound && lb == InclusiveBound))
+
+-- | Test if a version falls within the version intervals.
+--
+-- It exists mostly for completeness and testing. It satisfies the following
+-- properties:
+--
+-- > withinIntervals v (toVersionIntervals vr) = withinRange v vr
+-- > withinIntervals v ivs = withinRange v (fromVersionIntervals ivs)
+--
+withinIntervals :: Version -> VersionIntervals -> Bool
+withinIntervals v (VersionIntervals intervals) = any withinInterval intervals
+  where
+    withinInterval (lowerBound, upperBound)    = withinLower lowerBound
+                                              && withinUpper upperBound
+    withinLower (LowerBound v' ExclusiveBound) = v' <  v
+    withinLower (LowerBound v' InclusiveBound) = v' <= v
+
+    withinUpper NoUpperBound                   = True
+    withinUpper (UpperBound v' ExclusiveBound) = v' >  v
+    withinUpper (UpperBound v' InclusiveBound) = v' >= v
+
+-- | Convert a 'VersionRange' to a sequence of version intervals.
+--
+toVersionIntervals :: VersionRange -> VersionIntervals
+toVersionIntervals = cataVersionRange alg where
+    alg (ThisVersionF v)                = chkIvl (LowerBound v InclusiveBound, UpperBound v InclusiveBound)
+    alg (LaterVersionF v)               = chkIvl (LowerBound v ExclusiveBound, NoUpperBound)
+    alg (OrLaterVersionF v)             = chkIvl (LowerBound v InclusiveBound, NoUpperBound)
+    alg (EarlierVersionF v)
+        | isVersion0 v                  = VersionIntervals []
+        | otherwise                     = chkIvl (minLowerBound,               UpperBound v ExclusiveBound)
+    alg (OrEarlierVersionF v)           = chkIvl (minLowerBound,               UpperBound v InclusiveBound)
+    alg (MajorBoundVersionF v)          = chkIvl (LowerBound v InclusiveBound, UpperBound (majorUpperBound v) ExclusiveBound)
+    alg (UnionVersionRangesF v1 v2)     = unionVersionIntervals v1 v2
+    alg (IntersectVersionRangesF v1 v2) = intersectVersionIntervals v1 v2
+
+    chkIvl interval = checkInvariant (VersionIntervals [interval])
+
+-- | Convert a 'VersionIntervals' value back into a 'VersionRange' expression
+-- representing the version intervals.
+--
+fromVersionIntervals :: VersionIntervals -> VersionRange
+fromVersionIntervals (VersionIntervals []) = noVersion
+fromVersionIntervals (VersionIntervals intervals) =
+    Prelude.foldr1 unionVersionRanges [ interval l u | (l, u) <- intervals ]
+
+  where
+    interval (LowerBound v  InclusiveBound)
+             (UpperBound v' InclusiveBound) | v == v'
+                 = thisVersion v
+    interval l u = lowerBound l `intersectVersionRanges'` upperBound u
+
+    lowerBound (LowerBound v InclusiveBound)
+                              | isVersion0 v = Nothing
+                              | otherwise    = Just (orLaterVersion v)
+    lowerBound (LowerBound v ExclusiveBound) = Just (laterVersion v)
+
+    upperBound NoUpperBound                  = Nothing
+    upperBound (UpperBound v InclusiveBound) = Just (orEarlierVersion v)
+    upperBound (UpperBound v ExclusiveBound) = Just (earlierVersion v)
+
+    intersectVersionRanges' Nothing Nothing      = anyVersion
+    intersectVersionRanges' (Just vr) Nothing    = vr
+    intersectVersionRanges' Nothing (Just vr)    = vr
+    intersectVersionRanges' (Just vr) (Just vr') = intersectVersionRanges vr vr'
+
+unionVersionIntervals :: VersionIntervals -> VersionIntervals
+                      -> VersionIntervals
+unionVersionIntervals (VersionIntervals is0) (VersionIntervals is'0) =
+  checkInvariant (VersionIntervals (union is0 is'0))
+  where
+    union is []  = is
+    union [] is' = is'
+    union (i:is) (i':is') = case unionInterval i i' of
+      Left  Nothing    -> i  : union      is  (i' :is')
+      Left  (Just i'') ->      union      is  (i'':is')
+      Right Nothing    -> i' : union (i  :is)      is'
+      Right (Just i'') ->      union (i'':is)      is'
+
+unionInterval :: VersionInterval -> VersionInterval
+              -> Either (Maybe VersionInterval) (Maybe VersionInterval)
+unionInterval (lower , upper ) (lower', upper')
+
+  -- Non-intersecting intervals with the left interval ending first
+  | upper `doesNotTouch` lower' = Left Nothing
+
+  -- Non-intersecting intervals with the right interval first
+  | upper' `doesNotTouch` lower = Right Nothing
+
+  -- Complete or partial overlap, with the left interval ending first
+  | upper <= upper' = lowerBound `seq`
+                      Left (Just (lowerBound, upper'))
+
+  -- Complete or partial overlap, with the left interval ending first
+  | otherwise = lowerBound `seq`
+                Right (Just (lowerBound, upper))
+  where
+    lowerBound = min lower lower'
+
+intersectVersionIntervals :: VersionIntervals -> VersionIntervals
+                          -> VersionIntervals
+intersectVersionIntervals (VersionIntervals is0) (VersionIntervals is'0) =
+  checkInvariant (VersionIntervals (intersect is0 is'0))
+  where
+    intersect _  [] = []
+    intersect [] _  = []
+    intersect (i:is) (i':is') = case intersectInterval i i' of
+      Left  Nothing    ->       intersect is (i':is')
+      Left  (Just i'') -> i'' : intersect is (i':is')
+      Right Nothing    ->       intersect (i:is) is'
+      Right (Just i'') -> i'' : intersect (i:is) is'
+
+intersectInterval :: VersionInterval -> VersionInterval
+                  -> Either (Maybe VersionInterval) (Maybe VersionInterval)
+intersectInterval (lower , upper ) (lower', upper')
+
+  -- Non-intersecting intervals with the left interval ending first
+  | upper `doesNotIntersect` lower' = Left Nothing
+
+  -- Non-intersecting intervals with the right interval first
+  | upper' `doesNotIntersect` lower = Right Nothing
+
+  -- Complete or partial overlap, with the left interval ending first
+  | upper <= upper' = lowerBound `seq`
+                      Left (Just (lowerBound, upper))
+
+  -- Complete or partial overlap, with the right interval ending first
+  | otherwise = lowerBound `seq`
+                Right (Just (lowerBound, upper'))
+  where
+    lowerBound = max lower lower'
+
+invertVersionIntervals :: VersionIntervals
+                       -> VersionIntervals
+invertVersionIntervals (VersionIntervals xs) =
+    case xs of
+      -- Empty interval set
+      [] -> VersionIntervals [(noLowerBound, NoUpperBound)]
+      -- Interval with no lower bound
+      ((lb, ub) : more) | lb == noLowerBound ->
+        VersionIntervals $ invertVersionIntervals' ub more
+      -- Interval with a lower bound
+      ((lb, ub) : more) ->
+          VersionIntervals $ (noLowerBound, invertLowerBound lb)
+          : invertVersionIntervals' ub more
+    where
+      -- Invert subsequent version intervals given the upper bound of
+      -- the intervals already inverted.
+      invertVersionIntervals' :: UpperBound
+                              -> [(LowerBound, UpperBound)]
+                              -> [(LowerBound, UpperBound)]
+      invertVersionIntervals' NoUpperBound [] = []
+      invertVersionIntervals' ub0 [] = [(invertUpperBound ub0, NoUpperBound)]
+      invertVersionIntervals' ub0 [(lb, NoUpperBound)] =
+          [(invertUpperBound ub0, invertLowerBound lb)]
+      invertVersionIntervals' ub0 ((lb, ub1) : more) =
+          (invertUpperBound ub0, invertLowerBound lb)
+            : invertVersionIntervals' ub1 more
+
+      invertLowerBound :: LowerBound -> UpperBound
+      invertLowerBound (LowerBound v b) = UpperBound v (invertBound b)
+
+      invertUpperBound :: UpperBound -> LowerBound
+      invertUpperBound (UpperBound v b) = LowerBound v (invertBound b)
+      invertUpperBound NoUpperBound = error "NoUpperBound: unexpected"
+
+      invertBound :: Bound -> Bound
+      invertBound ExclusiveBound = InclusiveBound
+      invertBound InclusiveBound = ExclusiveBound
+
+      noLowerBound :: LowerBound
+      noLowerBound = LowerBound (mkVersion [0]) InclusiveBound
+
+
+relaxLastInterval :: VersionIntervals -> VersionIntervals
+relaxLastInterval (VersionIntervals xs) = VersionIntervals (relaxLastInterval' xs)
+  where
+    relaxLastInterval' []      = []
+    relaxLastInterval' [(l,_)] = [(l, NoUpperBound)]
+    relaxLastInterval' (i:is)  = i : relaxLastInterval' is
+
+relaxHeadInterval :: VersionIntervals -> VersionIntervals
+relaxHeadInterval (VersionIntervals xs) = VersionIntervals (relaxHeadInterval' xs)
+  where
+    relaxHeadInterval' []         = []
+    relaxHeadInterval' ((_,u):is) = (minLowerBound,u) : is
diff --git a/Cabal/src/Distribution/Types/VersionRange.hs b/Cabal/src/Distribution/Types/VersionRange.hs
index 2bd8a8f941..1f10688407 100644
--- a/Cabal/src/Distribution/Types/VersionRange.hs
+++ b/Cabal/src/Distribution/Types/VersionRange.hs
@@ -128,8 +128,8 @@ withinRange v = foldVersionRange
 --
 isAnyVersion :: VersionRange -> Bool
 isAnyVersion vr = case asVersionIntervals vr of
-    [(LowerBound v InclusiveBound, NoUpperBound)] -> v == version0
-    _                                             -> False
+    [VersionInterval (LowerBound v InclusiveBound) NoUpperBound] -> v == version0
+    _                                                            -> False
 
 -- A fast and non-precise version of 'isAnyVersion',
 -- returns 'True' only for @>= 0@ 'VersionRange's.
diff --git a/Cabal/src/Distribution/Version.hs b/Cabal/src/Distribution/Version.hs
index f282759043..33e2885af7 100644
--- a/Cabal/src/Distribution/Version.hs
+++ b/Cabal/src/Distribution/Version.hs
@@ -31,8 +31,6 @@ module Distribution.Version (
   laterVersion, earlierVersion,
   orLaterVersion, orEarlierVersion,
   unionVersionRanges, intersectVersionRanges,
-  differenceVersionRanges,
-  invertVersionRange,
   withinVersion,
   majorBoundVersion,
 
@@ -63,10 +61,13 @@ module Distribution.Version (
   -- ** Modification
   removeUpperBound,
   removeLowerBound,
+  transformCaret,
+  transformCaretUpper,
+  transformCaretLower,
 
   -- * Version intervals view
   asVersionIntervals,
-  VersionInterval,
+  VersionInterval(..),
   LowerBound(..),
   UpperBound(..),
   Bound(..),
@@ -80,12 +81,7 @@ module Distribution.Version (
   VersionIntervals,
   toVersionIntervals,
   fromVersionIntervals,
-  withinIntervals,
-  versionIntervals,
-  mkVersionIntervals,
-  unionVersionIntervals,
-  intersectVersionIntervals,
-  invertVersionIntervals
+  unVersionIntervals,
 
  ) where
 
@@ -116,11 +112,14 @@ isNoVersion vr = case asVersionIntervals vr of
 --
 isSpecificVersion :: VersionRange -> Maybe Version
 isSpecificVersion vr = case asVersionIntervals vr of
-  [(LowerBound v  InclusiveBound
-   ,UpperBound v' InclusiveBound)]
+  [VersionInterval (LowerBound v  InclusiveBound) (UpperBound v' InclusiveBound)]
     | v == v' -> Just v
   _           -> Nothing
 
+-------------------------------------------------------------------------------
+-- Transformations
+-------------------------------------------------------------------------------
+
 -- | Simplify a 'VersionRange' expression. For non-empty version ranges
 -- this produces a canonical form. Empty or inconsistent version ranges
 -- are left as-is because that provides more information.
@@ -142,30 +141,11 @@ simplifyVersionRange vr
     -- If the version range is inconsistent then we just return the
     -- original since that has more information than ">1 && < 1", which
     -- is the canonical inconsistent version range.
-    | null (versionIntervals vi) = vr
-    | otherwise                  = fromVersionIntervals vi
+    | null (unVersionIntervals vi) = vr
+    | otherwise                    = fromVersionIntervals vi
   where
     vi = toVersionIntervals vr
 
--- | The difference of two version ranges
---
--- >   withinRange v' (differenceVersionRanges vr1 vr2)
--- > = withinRange v' vr1 && not (withinRange v' vr2)
---
--- @since 1.24.1.0
-differenceVersionRanges :: VersionRange -> VersionRange -> VersionRange
-differenceVersionRanges vr1 vr2 =
-    intersectVersionRanges vr1 (invertVersionRange vr2)
-
--- | The inverse of a version range
---
--- >   withinRange v' (invertVersionRange vr)
--- > = not (withinRange v' vr)
---
-invertVersionRange :: VersionRange -> VersionRange
-invertVersionRange =
-    fromVersionIntervals . invertVersionIntervals . toVersionIntervals
-
 -- | Given a version range, remove the highest upper bound. Example: @(>= 1 && <
 -- 3) || (>= 4 && < 5)@ is converted to @(>= 1 && < 3) || (>= 4)@.
 removeUpperBound :: VersionRange -> VersionRange
@@ -176,3 +156,30 @@ removeUpperBound = fromVersionIntervals . relaxLastInterval . toVersionIntervals
 -- @(>= 0 && < 3) || (>= 4 && < 5)@.
 removeLowerBound :: VersionRange -> VersionRange
 removeLowerBound = fromVersionIntervals . relaxHeadInterval . toVersionIntervals
+
+-- | Rewrite @^>= x.y.z@ into @>= x.y.z && < x.(y+1)@
+--
+-- @since 3.6.0.0
+--
+transformCaret :: VersionRange -> VersionRange
+transformCaret = hyloVersionRange embed projectVersionRange where
+    embed (MajorBoundVersionF v) = orLaterVersion v `intersectVersionRanges` earlierVersion (majorUpperBound v)
+    embed vr                     = embedVersionRange vr
+
+-- | Rewrite @^>= x.y.z@ into @>= x.y.z@
+--
+-- @since 3.6.0.0
+--
+transformCaretUpper :: VersionRange -> VersionRange
+transformCaretUpper = hyloVersionRange embed projectVersionRange where
+    embed (MajorBoundVersionF v) = orLaterVersion v
+    embed vr                     = embedVersionRange vr
+
+-- | Rewrite @^>= x.y.z@ into @<x.(y+1)@
+--
+-- @since 3.6.0.0
+--
+transformCaretLower :: VersionRange -> VersionRange
+transformCaretLower = hyloVersionRange embed projectVersionRange where
+    embed (MajorBoundVersionF v) = earlierVersion (majorUpperBound v)
+    embed vr                     = embedVersionRange vr
diff --git a/Cabal/tests/UnitTests/Distribution/Version.hs b/Cabal/tests/UnitTests/Distribution/Version.hs
index 024ab84012..27d9f440af 100644
--- a/Cabal/tests/UnitTests/Distribution/Version.hs
+++ b/Cabal/tests/UnitTests/Distribution/Version.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE StandaloneDeriving, DeriveDataTypeable #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE StandaloneDeriving #-}
 {-# OPTIONS_GHC -fno-warn-incomplete-patterns
                 -fno-warn-deprecations
                 -fno-warn-unused-binds #-} --FIXME
@@ -7,23 +8,23 @@ module UnitTests.Distribution.Version (versionTests) where
 import Distribution.Compat.Prelude.Internal
 import Prelude ()
 
-import Distribution.Version
-import Distribution.Types.VersionRange.Internal
-import Distribution.Parsec (simpleParsec)
+import Distribution.Parsec                      (simpleParsec)
 import Distribution.Pretty
+import Distribution.Types.VersionRange.Internal
 import Distribution.Utils.Generic
+import Distribution.Version
 
-import Data.Typeable (typeOf)
-import Math.NumberTheory.Logarithms (intLog2)
-import Text.PrettyPrint as Disp (text, render, hcat
-                                ,punctuate, int, char)
-import Test.Tasty
-import Test.Tasty.QuickCheck
-import qualified Test.Laws as Laws
 
+import Data.Maybe                      (fromJust)
+import Data.Typeable                   (typeOf)
+import Test.QuickCheck                 (Arbitrary (..), NonEmptyList (..), NonNegative (..), Property, Testable, counterexample, property, (===), (==>), vectorOf, sized, choose, arbitrarySizedNatural)
 import Test.QuickCheck.Instances.Cabal ()
+import Test.Tasty                      (TestTree)
+import Test.Tasty.QuickCheck           (testProperty)
 
-import Data.Maybe (fromJust)
+import qualified Distribution.Types.VersionInterval        as New
+import qualified Distribution.Types.VersionInterval.Legacy as Old
+import qualified Text.PrettyPrint                          as Disp
 
 versionTests :: [TestTree]
 versionTests =
@@ -38,9 +39,17 @@ versionTests =
     , tp "readMaybe . show = Just"                             prop_ShowRead
     , tp "read example"                                        prop_ShowRead_example
 
-    , tp "normaliseVersionRange involutive"                    prop_normalise_inv
     , tp "parsec . prettyShow involutive"                      prop_parsec_disp_inv
 
+    , tp "normaliseVersionRange involutive"                    prop_normalise_inv
+    , tp "normaliseVersionRange equivalent"                    prop_normalise_equiv
+    , tp "normaliseVersionRange caretequiv"                    prop_normalise_caret_equiv
+    , tp "normaliseVersionRange model"                         prop_normalise_model
+
+    , tp "simplifyVersionRange involutive"                     prop_simplify_inv
+    , tp "simplifyVersionRange equivalent"                     prop_simplify_equiv
+    -- , tp "simplifyVersionRange caretequiv"                     prop_simplify_caret_equiv
+
     , tp "simpleParsec . prettyShow = Just" prop_parse_disp
     ]
 
@@ -52,8 +61,6 @@ versionTests =
     [ typProperty prop_nonNull
     , typProperty prop_gen_intervals1
     , typProperty prop_gen_intervals2
-  --, typProperty prop_equivalentVersionRange --FIXME: runs out of test cases
-    , typProperty prop_intermediateVersion
 
     , typProperty prop_anyVersion
     , typProperty prop_noVersion
@@ -65,47 +72,11 @@ versionTests =
     , typProperty prop_orEarlierVersion
     , typProperty prop_unionVersionRanges
     , typProperty prop_intersectVersionRanges
-    , typProperty prop_differenceVersionRanges
-    , typProperty prop_invertVersionRange
     , typProperty prop_withinVersion
     , typProperty prop_foldVersionRange
 
-      -- the semantic query functions
-  --, typProperty prop_isAnyVersion1       --FIXME: runs out of test cases
-  --, typProperty prop_isAnyVersion2       --FIXME: runs out of test cases
-  --, typProperty prop_isNoVersion         --FIXME: runs out of test cases
-  --, typProperty prop_isSpecificVersion1  --FIXME: runs out of test cases
-  --, typProperty prop_isSpecificVersion2  --FIXME: runs out of test cases
-    , typProperty prop_simplifyVersionRange1
-    , typProperty prop_simplifyVersionRange1'
-  --, typProperty prop_simplifyVersionRange2   --FIXME: runs out of test cases
-  --, typProperty prop_simplifyVersionRange2'  --FIXME: runs out of test cases
-  --, typProperty prop_simplifyVersionRange2'' --FIXME: actually wrong
-
       -- converting between version ranges and version intervals
-    , typProperty prop_to_intervals
-  --, typProperty prop_to_intervals_canonical  --FIXME: runs out of test cases
-  --, typProperty prop_to_intervals_canonical' --FIXME: runs out of test cases
-    , typProperty prop_from_intervals
     , typProperty prop_to_from_intervals
-    , typProperty prop_from_to_intervals
-    , typProperty prop_from_to_intervals'
-
-      -- union and intersection of version intervals
-    , typProperty prop_unionVersionIntervals
-    , typProperty prop_unionVersionIntervals_idempotent
-    , typProperty prop_unionVersionIntervals_commutative
-    , typProperty prop_unionVersionIntervals_associative
-    , typProperty prop_intersectVersionIntervals
-    , typProperty prop_intersectVersionIntervals_idempotent
-    , typProperty prop_intersectVersionIntervals_commutative
-    , typProperty prop_intersectVersionIntervals_associative
-    , typProperty prop_union_intersect_distributive
-    , typProperty prop_intersect_union_distributive
-
-      -- inversion of version intervals
-    , typProperty prop_invertVersionIntervals
-    , typProperty prop_invertVersionIntervalsTwice
     ]
   where
     tp :: Testable p => String -> p -> TestTree
@@ -113,21 +84,9 @@ versionTests =
 
     typProperty p = (typeOf p, property p)
 
-
--- parseTests :: [TestTree]
--- parseTests =
---   zipWith (\n p -> testProperty ("Parse Property " ++ show n) p) [1::Int ..]
---    -- parsing and pretty printing
---   [ -- property prop_parse_disp1  --FIXME: actually wrong
-
---     --  These are also wrong, see
---     --  https://github.com/haskell/cabal/issues/3037#issuecomment-177671011
-
---     --   property prop_parse_disp2
---     -- , property prop_parse_disp3
---     -- , property prop_parse_disp4
---     -- , property prop_parse_disp5
---   ]
+-------------------------------------------------------------------------------
+-- Arbitrary for inputs of mkVersion
+-------------------------------------------------------------------------------
 
 newtype VersionArb = VersionArb [Int]
                    deriving (Eq,Ord,Show)
@@ -190,8 +149,44 @@ prop_ShowRead_example = show (mkVersion [1,2,3]) == "mkVersion [1,2,3]"
 --
 
 prop_normalise_inv :: VersionRange -> Property
-prop_normalise_inv vr =
-    normaliseVersionRange vr === normaliseVersionRange (normaliseVersionRange vr)
+prop_normalise_inv vr = normaliseVersionRange vr === normaliseVersionRange (normaliseVersionRange vr)
+
+prop_normalise_equiv :: VersionRange -> Version -> Property
+prop_normalise_equiv vr =
+    prop_equivalentVersionRange vr (normaliseVersionRange vr)
+
+prop_normalise_caret_equiv :: VersionRange -> Version -> Property
+prop_normalise_caret_equiv vr = prop_equivalentVersionRange
+    (transformCaretUpper vr)
+    (transformCaretUpper (normaliseVersionRange vr))
+
+prop_normalise_model :: VersionRange -> Property
+prop_normalise_model vr =
+    oldNormaliseVersionRange vr' === newNormaliseVersionRange vr'
+  where
+    vr' = transformCaret vr
+
+    oldNormaliseVersionRange :: VersionRange -> VersionRange
+    oldNormaliseVersionRange = Old.fromVersionIntervals . Old.toVersionIntervals
+
+    newNormaliseVersionRange :: VersionRange -> VersionRange
+    newNormaliseVersionRange = New.normaliseVersionRange2
+
+prop_simplify_inv :: VersionRange -> Property
+prop_simplify_inv vr =
+    simplifyVersionRange vr === simplifyVersionRange (simplifyVersionRange vr)
+
+prop_simplify_equiv :: VersionRange -> Version -> Property
+prop_simplify_equiv vr v =
+    counterexample (show vr') $ prop_equivalentVersionRange vr vr' v
+  where
+    vr' = simplifyVersionRange vr
+
+-- TODO: Doesn't hold yet
+-- prop_simplify_caret_equiv :: VersionRange -> Version -> Property
+-- prop_simplify_caret_equiv vr = prop_equivalentVersionRange
+--     (transformCaretUpper vr)
+--     (transformCaretUpper (simplifyVersionRange vr))
 
 prop_nonNull :: Version -> Bool
 prop_nonNull = (/= nullVersion)
@@ -244,16 +239,6 @@ prop_intersectVersionRanges vr1 vr2 v' =
      withinRange v' (intersectVersionRanges vr1 vr2)
   == (withinRange v' vr1 && withinRange v' vr2)
 
-prop_differenceVersionRanges :: VersionRange -> VersionRange -> Version -> Bool
-prop_differenceVersionRanges vr1 vr2 v' =
-     withinRange v' (differenceVersionRanges vr1 vr2)
-  == (withinRange v' vr1 && not (withinRange v' vr2))
-
-prop_invertVersionRange :: VersionRange -> Version -> Bool
-prop_invertVersionRange vr v' =
-     withinRange v' (invertVersionRange vr)
-  == not (withinRange v' vr)
-
 prop_withinVersion :: Version -> Version -> Property
 prop_withinVersion v v' =
     withinRange v' (withinVersion v)
@@ -323,49 +308,12 @@ prop_isSpecificVersion2 range =
   where
     version = isSpecificVersion range
 
--- | 'simplifyVersionRange' is a semantic identity on 'VersionRange'.
---
-prop_simplifyVersionRange1 :: VersionRange -> Version -> Bool
-prop_simplifyVersionRange1 range version =
-  withinRange version range == withinRange version (simplifyVersionRange range)
-
-prop_simplifyVersionRange1' :: VersionRange -> Bool
-prop_simplifyVersionRange1' range =
-  range `equivalentVersionRange` (simplifyVersionRange range)
-
--- | 'simplifyVersionRange' produces a canonical form for ranges with
--- equivalent semantics.
---
-prop_simplifyVersionRange2 :: VersionRange -> VersionRange -> Version -> Property
-prop_simplifyVersionRange2 r r' v =
-  r /= r' && simplifyVersionRange r == simplifyVersionRange r' ==>
-    withinRange v r == withinRange v r'
-
-prop_simplifyVersionRange2' :: VersionRange -> VersionRange -> Property
-prop_simplifyVersionRange2' r r' =
-  r /= r' && simplifyVersionRange r == simplifyVersionRange r' ==>
-    r `equivalentVersionRange` r'
-
---FIXME: see equivalentVersionRange for details
-prop_simplifyVersionRange2'' :: VersionRange -> VersionRange -> Property
-prop_simplifyVersionRange2'' r r' =
-  r /= r' && r `equivalentVersionRange` r' ==>
-       simplifyVersionRange r == simplifyVersionRange r'
-    || isNoVersion r
-    || isNoVersion r'
-
 -- | Check that our VersionIntervals' arbitrary instance generates intervals
 -- that satisfies the invariant.
 --
 prop_gen_intervals1 :: VersionIntervals -> Property
-prop_gen_intervals1 i
-    = label ("length i ≈ 2 ^ " ++ show metric ++ " - 1")
-    $ xs === ys
-  where
-    metric = intLog2 (length xs + 1)
+prop_gen_intervals1 = property . New.invariantVersionIntervals
 
-    xs = versionIntervals i
-    ys = versionIntervals (mkVersionIntervals xs)
 -- | Check that constructing our intervals type and converting it to a
 -- 'VersionRange' and then into the true intervals type gives us back
 -- the exact same sequence of intervals. This tells us that our arbitrary
@@ -374,38 +322,7 @@ prop_gen_intervals1 i
 prop_gen_intervals2 :: VersionIntervals -> Property
 prop_gen_intervals2 intervals =
     toVersionIntervals (fromVersionIntervals intervals) === intervals
-
--- | Check that 'VersionIntervals' models 'VersionRange' via
--- 'toVersionIntervals'.
 --
-prop_to_intervals :: VersionRange -> Version -> Bool
-prop_to_intervals range version =
-  withinRange version range == withinIntervals version intervals
-  where
-    intervals = toVersionIntervals range
-
--- | Check that semantic equality on 'VersionRange's is the same as converting
--- to 'VersionIntervals' and doing syntactic equality.
---
-prop_to_intervals_canonical :: VersionRange -> VersionRange -> Property
-prop_to_intervals_canonical r r' =
-  r /= r' && r `equivalentVersionRange` r' ==>
-    toVersionIntervals r == toVersionIntervals r'
-
-prop_to_intervals_canonical' :: VersionRange -> VersionRange -> Property
-prop_to_intervals_canonical' r r' =
-  r /= r' && toVersionIntervals r == toVersionIntervals r' ==>
-    r `equivalentVersionRange` r'
-
--- | Check that 'VersionIntervals' models 'VersionRange' via
--- 'fromVersionIntervals'.
---
-prop_from_intervals :: VersionIntervals -> Version -> Bool
-prop_from_intervals intervals version =
-  withinRange version range == withinIntervals version intervals
-  where
-    range = fromVersionIntervals intervals
-
 -- | @'toVersionIntervals' . 'fromVersionIntervals'@ is an exact identity on
 -- 'VersionIntervals'.
 --
@@ -413,167 +330,13 @@ prop_to_from_intervals :: VersionIntervals -> Bool
 prop_to_from_intervals intervals =
   toVersionIntervals (fromVersionIntervals intervals) == intervals
 
--- | @'fromVersionIntervals' . 'toVersionIntervals'@ is a semantic identity on
--- 'VersionRange', though not necessarily a syntactic identity.
---
-prop_from_to_intervals :: VersionRange -> Bool
-prop_from_to_intervals range =
-  range' `equivalentVersionRange` range
-  where
-    range' = fromVersionIntervals (toVersionIntervals range)
-
--- | Equivalent of 'prop_from_to_intervals'
---
-prop_from_to_intervals' :: VersionRange -> Version -> Bool
-prop_from_to_intervals' range version =
-  withinRange version range' == withinRange version range
-  where
-    range' = fromVersionIntervals (toVersionIntervals range)
-
--- | The semantics of 'unionVersionIntervals' is (||).
---
-prop_unionVersionIntervals :: VersionIntervals -> VersionIntervals
-                           -> Version -> Bool
-prop_unionVersionIntervals is1 is2 v =
-     withinIntervals v (unionVersionIntervals is1 is2)
-  == (withinIntervals v is1 || withinIntervals v is2)
-
--- | 'unionVersionIntervals' is idempotent
---
-prop_unionVersionIntervals_idempotent :: VersionIntervals -> Bool
-prop_unionVersionIntervals_idempotent =
-  Laws.idempotent_binary unionVersionIntervals
-
--- | 'unionVersionIntervals' is commutative
---
-prop_unionVersionIntervals_commutative :: VersionIntervals
-                                       -> VersionIntervals -> Bool
-prop_unionVersionIntervals_commutative =
-  Laws.commutative unionVersionIntervals
-
--- | 'unionVersionIntervals' is associative
---
-prop_unionVersionIntervals_associative :: VersionIntervals
-                                       -> VersionIntervals
-                                       -> VersionIntervals -> Bool
-prop_unionVersionIntervals_associative =
-  Laws.associative unionVersionIntervals
-
--- | The semantics of 'intersectVersionIntervals' is (&&).
---
-prop_intersectVersionIntervals :: VersionIntervals -> VersionIntervals
-                               -> Version -> Bool
-prop_intersectVersionIntervals is1 is2 v =
-     withinIntervals v (intersectVersionIntervals is1 is2)
-  == (withinIntervals v is1 && withinIntervals v is2)
-
--- | 'intersectVersionIntervals' is idempotent
---
-prop_intersectVersionIntervals_idempotent :: VersionIntervals -> Bool
-prop_intersectVersionIntervals_idempotent =
-  Laws.idempotent_binary intersectVersionIntervals
-
--- | 'intersectVersionIntervals' is commutative
---
-prop_intersectVersionIntervals_commutative :: VersionIntervals
-                                           -> VersionIntervals -> Bool
-prop_intersectVersionIntervals_commutative =
-  Laws.commutative intersectVersionIntervals
-
--- | 'intersectVersionIntervals' is associative
---
-prop_intersectVersionIntervals_associative :: VersionIntervals
-                                           -> VersionIntervals
-                                           -> VersionIntervals -> Bool
-prop_intersectVersionIntervals_associative =
-  Laws.associative intersectVersionIntervals
-
--- | 'unionVersionIntervals' distributes over 'intersectVersionIntervals'
---
-prop_union_intersect_distributive :: Property
-prop_union_intersect_distributive =
-      Laws.distributive_left  unionVersionIntervals intersectVersionIntervals
-  .&. Laws.distributive_right unionVersionIntervals intersectVersionIntervals
-
--- | 'intersectVersionIntervals' distributes over 'unionVersionIntervals'
---
-prop_intersect_union_distributive :: Property
-prop_intersect_union_distributive =
-      Laws.distributive_left  intersectVersionIntervals unionVersionIntervals
-  .&. Laws.distributive_right intersectVersionIntervals unionVersionIntervals
-
--- | The semantics of 'invertVersionIntervals' is 'not'.
---
-prop_invertVersionIntervals :: VersionIntervals
-                               -> Version -> Bool
-prop_invertVersionIntervals vi v =
-     withinIntervals v (invertVersionIntervals vi)
-  == not (withinIntervals v vi)
-
--- | Double application of 'invertVersionIntervals' is the identity function
-prop_invertVersionIntervalsTwice :: VersionIntervals -> Bool
-prop_invertVersionIntervalsTwice vi =
-    invertVersionIntervals (invertVersionIntervals vi) == vi
-
-
-
 --------------------------------
 -- equivalentVersionRange helper
 
-prop_equivalentVersionRange :: VersionRange  -> VersionRange
-                            -> Version -> Property
+prop_equivalentVersionRange
+    :: VersionRange  -> VersionRange -> Version -> Property
 prop_equivalentVersionRange range range' version =
-  equivalentVersionRange range range' && range /= range' ==>
-    withinRange version range == withinRange version range'
-
---FIXME: this is wrong. consider version ranges "<=1" and "<1.0"
---       this algorithm cannot distinguish them because there is no version
---       that is included by one that is excluded by the other.
---       Alternatively we must reconsider the semantics of '<' and '<='
---       in version ranges / version intervals. Perhaps the canonical
---       representation should use just < v and interpret "<= v" as "< v.0".
-equivalentVersionRange :: VersionRange -> VersionRange -> Bool
-equivalentVersionRange vr1 vr2 =
-  let allVersionsUsed = nub (sort (versionsUsed vr1 ++ versionsUsed vr2))
-      minPoint = mkVersion [0]
-      maxPoint | null allVersionsUsed = minPoint
-               | otherwise = alterVersion (++[1]) (maximum allVersionsUsed)
-      probeVersions = minPoint : maxPoint
-                    : intermediateVersions allVersionsUsed
-
-  in all (\v -> withinRange v vr1 == withinRange v vr2) probeVersions
-
-  where
-    versionsUsed = foldVersionRange [] (\x->[x]) (\x->[x]) (\x->[x]) (++) (++)
-    intermediateVersions (v1:v2:vs) = v1 : intermediateVersion v1 v2
-                                         : intermediateVersions (v2:vs)
-    intermediateVersions vs = vs
-
-intermediateVersion :: Version -> Version -> Version
-intermediateVersion v1 v2 | v1 >= v2 = error "intermediateVersion: v1 >= v2"
-intermediateVersion v1 v2 =
-  mkVersion (intermediateList (versionNumbers v1) (versionNumbers v2))
-  where
-    intermediateList :: [Int] -> [Int] -> [Int]
-    intermediateList []     (_:_) = [0]
-    intermediateList (x:xs) (y:ys)
-        | x <  y    = x : xs ++ [0]
-        | otherwise = x : intermediateList xs ys
-
-prop_intermediateVersion :: Version -> Version -> Property
-prop_intermediateVersion v1 v2 =
-  (v1 /= v2) && not (adjacentVersions v1 v2) ==>
-  if v1 < v2
-    then let v = intermediateVersion v1 v2
-          in (v1 < v && v < v2)
-    else let v = intermediateVersion v2 v1
-          in v1 > v && v > v2
-
-adjacentVersions :: Version -> Version -> Bool
-adjacentVersions ver1 ver2 = v1 ++ [0] == v2 || v2 ++ [0] == v1
-  where
-    v1 = versionNumbers ver1
-    v2 = versionNumbers ver2
+    withinRange version range === withinRange version range'
 
 --------------------------------
 -- Parsing and pretty printing
diff --git a/cabal-benchmarks/bench/CabalBenchmarks.hs b/cabal-benchmarks/bench/CabalBenchmarks.hs
index f63a475881..65d698db0f 100644
--- a/cabal-benchmarks/bench/CabalBenchmarks.hs
+++ b/cabal-benchmarks/bench/CabalBenchmarks.hs
@@ -1,14 +1,115 @@
+{-# OPTIONS -Wno-deprecations #-}
 module Main where
 
-import Criterion.Main                         (bench, defaultMain, env, whnf)
+import Criterion.Main                         (bench, bgroup, defaultMain, env, nf, whnf)
 import Distribution.PackageDescription.Parsec (parseGenericPackageDescriptionMaybe)
+import Distribution.Parsec                    (eitherParsec)
+import Distribution.Version
 
 import qualified Data.ByteString as BS
 
+import qualified Distribution.Types.VersionInterval.Legacy as Old
+import qualified Distribution.Types.VersionInterval        as New
+
+-------------------------------------------------------------------------------
+-- Main
+-------------------------------------------------------------------------------
+
 main :: IO ()
 main = defaultMain
     [ env (BS.readFile "Cabal/Cabal.cabal") $ \bs ->
       bench "Cabal" $ whnf parseGenericPackageDescriptionMaybe bs
     , env (BS.readFile "cabal-benchmarks/cabal-benchmarks.cabal") $ \bs ->
       bench "cabal-benchmarks" $ whnf parseGenericPackageDescriptionMaybe bs
+
+    , bgroup "normaliseVersionRange" $
+        let suite name f = bgroup name
+                [ env bigVersionRange1 $ \vr -> bench "dnf1" $ nf f vr
+                , env bigVersionRange2 $ \vr -> bench "dnf2" $ nf f vr
+                , env bigVersionRange3 $ \vr -> bench "cnf1" $ nf f vr
+                , env bigVersionRange4 $ \vr -> bench "cnf2" $ nf f vr
+                , env bigVersionRange5 $ \vr -> bench "mix1" $ nf f vr
+                , env bigVersionRange6 $ \vr -> bench "mix2" $ nf f vr
+                , env bigVersionRange7 $ \vr -> bench "pat1" $ nf f vr
+                , env bigVersionRange8 $ \vr -> bench "pat2" $ nf f vr
+                , env bigVersionRange9 $ \vr -> bench "pat3" $ nf f vr
+                , env bigVersionRangeA $ \vr -> bench "pat4" $ nf f vr
+                ]
+        in  [ suite "def" normaliseVersionRange
+            , suite "old" oldNormaliseVersionRange
+            , suite "new" newNormaliseVersionRange
+            ]
     ]
+
+-------------------------------------------------------------------------------
+-- VersionRanges normalisation
+-------------------------------------------------------------------------------
+
+oldNormaliseVersionRange :: VersionRange -> VersionRange
+oldNormaliseVersionRange = Old.fromVersionIntervals . Old.toVersionIntervals
+
+newNormaliseVersionRange :: VersionRange -> VersionRange
+newNormaliseVersionRange = New.normaliseVersionRange2
+
+bigVersionRange1 :: IO VersionRange
+bigVersionRange1 = either fail return $ eitherParsec
+    "(>=1.2.0 && <1.3) || (>=1.3.0 && <1.4) || (>=1.4.0.0 && <1.5) || (>=1.5.0.0 && <1.6) || (>=1.7.0.0 && <1.8)"
+
+bigVersionRange2 :: IO VersionRange
+bigVersionRange2 = either fail return $ eitherParsec
+    "(>=1.2.0 && <1.3) || (>=1.4.0.0 && <1.5) || (>=1.3.0 && <1.4) || (>=1.5.0.0 && <1.6) || (>=1.7.0.0 && <1.8)"
+
+bigVersionRange3 :: IO VersionRange
+bigVersionRange3 = either fail return $ eitherParsec
+    ">=1.2.0 && (<1.3 || >=1.3.0) && (<1.4 || >=1.4.0.0) && (<1.5 || >=1.5.0.0) && (<1.6 || >=1.7.0.0) && <1.8"
+
+bigVersionRange4 :: IO VersionRange
+bigVersionRange4 = either fail return $ eitherParsec
+    ">=1.2.0 && <1.8 && (<1.4 || >=1.4.0.0) && (<1.3 || >=1.3.0) && (<1.5 || >=1.5.0.0) || (<1.6 && >=1.7.0.0)"
+
+bigVersionRange5 :: IO VersionRange
+bigVersionRange5 = either fail return $ eitherParsec
+    ">=1.2.0 && (<1.3 || >=1.3.0) && (<1.4 || (>=1.4.0.0 && <1.5) || >=1.5.0.0) && (<1.6 || (>=1.7.0.0 && (<1.8 || >=1.9) && <1.10) || >=1.11) && <1.12"
+
+bigVersionRange6 :: IO VersionRange
+bigVersionRange6 = fmap New.normaliseVersionRange2 bigVersionRange5
+
+bigVersionRange7 :: IO VersionRange
+bigVersionRange7 = return $
+    i2 $ i2 $ u (b 0 1) (b 0 1)
+  where
+    i2 x = i x x
+    i = intersectVersionRanges
+    u = unionVersionRanges
+    b x y = intersectVersionRanges (laterVersion (v x)) (earlierVersion (v y))
+    v x = mkVersion [x]
+
+bigVersionRange8 :: IO VersionRange
+bigVersionRange8 = return $
+    i2 $ i2 $ i2 $ u (b 0 1) (b 0 1)
+  where
+    i2 x = i x x
+    i = intersectVersionRanges
+    u = unionVersionRanges
+    b x y = intersectVersionRanges (laterVersion (v x)) (earlierVersion (v y))
+    v x = mkVersion [x]
+
+bigVersionRange9 :: IO VersionRange
+bigVersionRange9 = return $
+    i2 $ i2 $ i2 $ i2 $ u (b 0 1) (b 0 1)
+  where
+    i2 x = i x x
+    i = intersectVersionRanges
+    u = unionVersionRanges
+    b x y = intersectVersionRanges (laterVersion (v x)) (earlierVersion (v y))
+    v x = mkVersion [x]
+
+bigVersionRangeA :: IO VersionRange
+bigVersionRangeA = return $
+    i2 $ i2 $ i2 $ i2 $ i2 $ u (b 0 1) (b 0 1)
+  where
+    i2 x = i x x
+    i = intersectVersionRanges
+    u = unionVersionRanges
+    b x y = intersectVersionRanges (laterVersion (v x)) (earlierVersion (v y))
+    v x = mkVersion [x]
diff --git a/cabal-benchmarks/cabal-benchmarks.cabal b/cabal-benchmarks/cabal-benchmarks.cabal
index cc1df4789e..b06e97271a 100644
--- a/cabal-benchmarks/cabal-benchmarks.cabal
+++ b/cabal-benchmarks/cabal-benchmarks.cabal
@@ -19,7 +19,7 @@ extra-source-files: README.md
 source-repository head
   type:     git
   location: https://github.com/haskell/cabal/
-  subdir:   solver-benchmarks
+  subdir:   cabal-benchmarks
 
 test-suite cabal-benchmarks
   default-language: Haskell2010
diff --git a/cabal-install/src/Distribution/Client/Dependency.hs b/cabal-install/src/Distribution/Client/Dependency.hs
index e3312c0263..14025d74d8 100644
--- a/cabal-install/src/Distribution/Client/Dependency.hs
+++ b/cabal-install/src/Distribution/Client/Dependency.hs
@@ -513,18 +513,10 @@ relaxPackageDeps relKind (RelaxDepsSome depsToRelax0) gpd =
 
 -- | Internal helper for 'relaxPackageDeps'
 removeBound :: RelaxKind -> RelaxDepMod -> VersionRange -> VersionRange
-removeBound RelaxLower RelaxDepModNone = removeLowerBound
-removeBound RelaxUpper RelaxDepModNone = removeUpperBound
-removeBound relKind RelaxDepModCaret = hyloVersionRange embed projectVersionRange
-  where
-    embed (MajorBoundVersionF v) = caretTransformation v (majorUpperBound v)
-    embed vr                     = embedVersionRange vr
-
-    -- This function is the interesting part as it defines the meaning
-    -- of 'RelaxDepModCaret', i.e. to transform only @^>=@ constraints;
-    caretTransformation l u = case relKind of
-      RelaxUpper -> orLaterVersion l -- rewrite @^>= x.y.z@ into @>= x.y.z@
-      RelaxLower -> earlierVersion u -- rewrite @^>= x.y.z@ into @< x.(y+1)@
+removeBound RelaxLower RelaxDepModNone  = removeLowerBound
+removeBound RelaxUpper RelaxDepModNone  = removeUpperBound
+removeBound RelaxLower RelaxDepModCaret = transformCaretLower
+removeBound RelaxUpper RelaxDepModCaret = transformCaretUpper
 
 -- | Supply defaults for packages without explicit Setup dependencies
 --
diff --git a/cabal-install/src/Distribution/Client/GenBounds.hs b/cabal-install/src/Distribution/Client/GenBounds.hs
index eccfd021f8..5fe5ffd8ba 100644
--- a/cabal-install/src/Distribution/Client/GenBounds.hs
+++ b/cabal-install/src/Distribution/Client/GenBounds.hs
@@ -17,7 +17,6 @@ module Distribution.Client.GenBounds (
 
 import Prelude ()
 import Distribution.Client.Compat.Prelude
-import Distribution.Utils.Generic (safeLast)
 
 import Distribution.Client.Init
          ( incVersion )
@@ -45,19 +44,12 @@ import Distribution.Simple.Utils
 import Distribution.System
          ( Platform )
 import Distribution.Version
-         ( Version, alterVersion
+         ( Version, alterVersion, VersionInterval (..)
          , LowerBound(..), UpperBound(..), VersionRange, asVersionIntervals
-         , orLaterVersion, earlierVersion, intersectVersionRanges )
+         , orLaterVersion, earlierVersion, intersectVersionRanges, hasUpperBound)
 import System.Directory
          ( getCurrentDirectory )
 
--- | Does this version range have an upper bound?
-hasUpperBound :: VersionRange -> Bool
-hasUpperBound vr =
-    case safeLast (asVersionIntervals vr) of
-      Nothing -> False
-      Just l  -> if snd l == NoUpperBound then False else True
-
 -- | Given a version, return an API-compatible (according to PVP) version range.
 --
 -- Example: @0.4.1.2@ produces the version range @>= 0.4.1 && < 0.5@.
@@ -78,15 +70,16 @@ pvpize v = orLaterVersion (vn 3)
 showBounds :: Package pkg => Int -> pkg -> String
 showBounds padTo p = unwords $
     (padAfter padTo $ unPackageName $ packageName p) :
+    -- TODO: use normaliseVersionRange
     map showInterval (asVersionIntervals $ pvpize $ packageVersion p)
   where
     padAfter :: Int -> String -> String
     padAfter n str = str ++ replicate (n - length str) ' '
 
-    showInterval :: (LowerBound, UpperBound) -> String
-    showInterval (LowerBound _ _, NoUpperBound) =
+    showInterval :: VersionInterval -> String
+    showInterval (VersionInterval (LowerBound _ _) NoUpperBound) =
       error "Error: expected upper bound...this should never happen!"
-    showInterval (LowerBound l _, UpperBound u _) =
+    showInterval (VersionInterval (LowerBound l _) (UpperBound u _)) =
       unwords [">=", prettyShow l, "&& <", prettyShow u]
 
 -- | Entry point for the @gen-bounds@ command.
diff --git a/cabal-install/src/Distribution/Client/Outdated.hs b/cabal-install/src/Distribution/Client/Outdated.hs
index b153c3ab5d..b4a781c183 100644
--- a/cabal-install/src/Distribution/Client/Outdated.hs
+++ b/cabal-install/src/Distribution/Client/Outdated.hs
@@ -43,7 +43,7 @@ import Distribution.Types.Dependency
        (Dependency(..))
 import Distribution.Verbosity                        (silent)
 import Distribution.Version
-       (Version, VersionRange, LowerBound(..), UpperBound(..)
+       (Version, VersionInterval (..), VersionRange, LowerBound(..), UpperBound(..)
        ,asVersionIntervals, majorBoundVersion)
 import Distribution.PackageDescription.Parsec
        (readGenericPackageDescription)
@@ -207,7 +207,7 @@ listOutdated deps pkgIndex (ListOutdatedSettings ignorePred minorPred) =
     relaxMinor vr =
       let vis = asVersionIntervals vr
       in maybe vr relax (safeLast vis)
-      where relax (LowerBound v0 _, upper) =
+      where relax (VersionInterval (LowerBound v0 _) upper) =
               case upper of
                 NoUpperBound     -> vr
                 UpperBound _v1 _ -> majorBoundVersion v0
-- 
GitLab