Version.hs 39.3 KB
Newer Older
1
{-# LANGUAGE DeriveDataTypeable #-}
ttuegel's avatar
ttuegel committed
2
3
{-# LANGUAGE DeriveGeneric #-}

4
5
6
-----------------------------------------------------------------------------
-- |
-- Module      :  Distribution.Version
simonmar's avatar
simonmar committed
7
-- Copyright   :  Isaac Jones, Simon Marlow 2003-2004
8
--                Duncan Coutts 2008
9
-- License     :  BSD3
10
--
Duncan Coutts's avatar
Duncan Coutts committed
11
-- Maintainer  :  cabal-devel@haskell.org
ijones's avatar
ijones committed
12
-- Portability :  portable
13
--
Duncan Coutts's avatar
Duncan Coutts committed
14
15
16
-- Exports the 'Version' type along with a parser and pretty printer. A version
-- is something like @\"1.3.3\"@. It also defines the 'VersionRange' data
-- types. Version ranges are like @\">= 1.2 && < 2\"@.
17

simonmar's avatar
simonmar committed
18
19
module Distribution.Version (
  -- * Package versions
20
21
22
23
24
25
  Version,
  mkVersion,
  mkVersion',
  versionNumbers,
  nullVersion,
  alterVersion,
simonmar's avatar
simonmar committed
26

simonmar's avatar
simonmar committed
27
  -- * Version ranges
28
29
30
31
32
33
  VersionRange(..),

  -- ** Constructing
  anyVersion, noVersion,
  thisVersion, notThisVersion,
  laterVersion, earlierVersion,
simonmar's avatar
simonmar committed
34
  orLaterVersion, orEarlierVersion,
35
  unionVersionRanges, intersectVersionRanges,
Simon Jakobi's avatar
Simon Jakobi committed
36
  differenceVersionRanges,
37
  invertVersionRange,
38
  withinVersion,
39
  majorBoundVersion,
simonmar's avatar
simonmar committed
40
  betweenVersionsInclusive,
41

42
  -- ** Inspection
simonmar's avatar
simonmar committed
43
  withinRange,
44
  isAnyVersion,
45
  isNoVersion,
46
47
48
  isSpecificVersion,
  simplifyVersionRange,
  foldVersionRange,
49
  foldVersionRange',
50
51
  hasUpperBound,
  hasLowerBound,
simonmar's avatar
simonmar committed
52

53
  -- ** Modification
54
  removeUpperBound,
55
  removeLowerBound,
56

57
  -- * Version intervals view
58
59
  asVersionIntervals,
  VersionInterval,
60
61
62
  LowerBound(..),
  UpperBound(..),
  Bound(..),
63
64
65

  -- ** 'VersionIntervals' abstract type
  -- | The 'VersionIntervals' type and the accompanying functions are exposed
66
  -- primarily for completeness and testing purposes. In practice
67
68
69
70
  -- 'asVersionIntervals' is the main function to use to
  -- view a 'VersionRange' as a bunch of 'VersionInterval's.
  --
  VersionIntervals,
71
72
73
  toVersionIntervals,
  fromVersionIntervals,
  withinIntervals,
74
  versionIntervals,
75
76
77
  mkVersionIntervals,
  unionVersionIntervals,
  intersectVersionIntervals,
78
  invertVersionIntervals
79

simonmar's avatar
simonmar committed
80
81
 ) where

82
83
import Prelude ()
import Distribution.Compat.Prelude
84
import qualified Data.Version as Base
85
import Data.Bits (shiftL, shiftR, (.|.), (.&.))
simonmar's avatar
simonmar committed
86

87
import Distribution.Text
88
import qualified Distribution.Compat.ReadP as Parse
89
90
import Distribution.Compat.ReadP hiding (get)

91
import qualified Text.PrettyPrint as Disp
92
import Text.PrettyPrint ((<+>))
93
import Control.Exception (assert)
94

Oleg Grenrus's avatar
Oleg Grenrus committed
95
96
import qualified Text.Read as Read

97
98
99
100
101
102
103
104
105
106
107
108
109
110
-- -----------------------------------------------------------------------------
-- Versions

-- | A 'Version' represents the version of a software entity.
--
-- Instances of 'Eq' and 'Ord' are provided, which gives exact
-- equality and lexicographic ordering of the version number
-- components (i.e. 2.1 > 2.0, 1.2.3 > 1.2.2, etc.).
--
-- This type is opaque and distinct from the 'Base.Version' type in
-- "Data.Version" since @Cabal-2.0@. The difference extends to the
-- 'Binary' instance using a different (and more compact) encoding.
--
-- @since 2.0
111
112
113
114
115
116
117
data Version = PV0 {-# UNPACK #-} !Word64
             | PV1 !Int [Int]
             -- NOTE: If a version fits into the packed Word64
             -- representation (i.e. at most four version components
             -- which all fall into the [0..0xfffe] range), then PV0
             -- MUST be used. This is essential for the 'Eq' instance
             -- to work.
Oleg Grenrus's avatar
Oleg Grenrus committed
118
             deriving (Data,Eq,Generic,Typeable)
119
120

instance Ord Version where
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
    compare (PV0 x)    (PV0 y)    = compare x y
    compare (PV1 x xs) (PV1 y ys) = case compare x y of
        EQ -> compare xs ys
        c  -> c
    compare (PV0 w)    (PV1 y ys) = case compare x y of
        EQ -> compare [x2,x3,x4] ys
        c  -> c
      where
        x  = fromIntegral ((w `shiftR` 48) .&. 0xffff) - 1
        x2 = fromIntegral ((w `shiftR` 32) .&. 0xffff) - 1
        x3 = fromIntegral ((w `shiftR` 16) .&. 0xffff) - 1
        x4 = fromIntegral               (w .&. 0xffff) - 1
    compare (PV1 x xs) (PV0 w)    = case compare x y of
        EQ -> compare xs [y2,y3,y4]
        c  -> c
      where
        y  = fromIntegral ((w `shiftR` 48) .&. 0xffff) - 1
        y2 = fromIntegral ((w `shiftR` 32) .&. 0xffff) - 1
        y3 = fromIntegral ((w `shiftR` 16) .&. 0xffff) - 1
        y4 = fromIntegral               (w .&. 0xffff) - 1
141

Oleg Grenrus's avatar
Oleg Grenrus committed
142
143
144
145
146
147
148
149
150
151
152
instance Show Version where
    showsPrec d v = showParen (d > 10)
        $ showString "mkVersion "
        . showsPrec 11 (versionNumbers v)

instance Read Version where
    readPrec = Read.parens $ do
        Read.Ident "mkVersion" <- Read.lexP
        v <- Read.step Read.readPrec
        return (mkVersion v)

153
154
155
instance Binary Version

instance NFData Version where
156
157
    rnf (PV0 _) = ()
    rnf (PV1 _ ns) = rnf ns
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183

instance Text Version where
  disp ver
    = Disp.hcat (Disp.punctuate (Disp.char '.')
                                (map Disp.int $ versionNumbers ver))

  parse = do
      branch <- Parse.sepBy1 parseNat (Parse.char '.')
                -- allow but ignore tags:
      _tags  <- Parse.many (Parse.char '-' >> Parse.munch1 isAlphaNum)
      return (mkVersion branch)
    where
      parseNat = read `fmap` Parse.munch1 isDigit

-- | Construct 'Version' from list of version number components.
--
-- For instance, @mkVersion [3,2,1]@ constructs a 'Version'
-- representing the version @3.2.1@.
--
-- All version components must be non-negative. @mkVersion []@
-- currently represents the special /null/ version; see also 'nullVersion'.
--
-- @since 2.0
mkVersion :: [Int] -> Version
-- TODO: add validity check; disallow 'mkVersion []' (we have
-- 'nullVersion' for that)
184
185
186
187
mkVersion []                    = nullVersion
mkVersion (v1:[])
  | inWord16VerRep1 v1          = PV0 (mkWord64VerRep1 v1)
  | otherwise                   = PV1 v1 []
188
  where
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
    inWord16VerRep1 x1 = inWord16 (x1 .|. (x1+1))
    mkWord64VerRep1 y1 = mkWord64VerRep (y1+1) 0 0 0

mkVersion (v1:vs@(v2:[]))
  | inWord16VerRep2 v1 v2       = PV0 (mkWord64VerRep2 v1 v2)
  | otherwise                   = PV1 v1 vs
  where
    inWord16VerRep2 x1 x2 = inWord16 (x1 .|. (x1+1)
                                  .|. x2 .|. (x2+1))
    mkWord64VerRep2 y1 y2 = mkWord64VerRep (y1+1) (y2+1) 0 0

mkVersion (v1:vs@(v2:v3:[]))
  | inWord16VerRep3 v1 v2 v3    = PV0 (mkWord64VerRep3 v1 v2 v3)
  | otherwise                   = PV1 v1 vs
  where
    inWord16VerRep3 x1 x2 x3 = inWord16 (x1 .|. (x1+1)
                                     .|. x2 .|. (x2+1)
                                     .|. x3 .|. (x3+1))
    mkWord64VerRep3 y1 y2 y3 = mkWord64VerRep (y1+1) (y2+1) (y3+1) 0

mkVersion (v1:vs@(v2:v3:v4:[]))
  | inWord16VerRep4 v1 v2 v3 v4 = PV0 (mkWord64VerRep4 v1 v2 v3 v4)
  | otherwise                   = PV1 v1 vs
  where
    inWord16VerRep4 x1 x2 x3 x4 = inWord16 (x1 .|. (x1+1)
                                        .|. x2 .|. (x2+1)
                                        .|. x3 .|. (x3+1)
                                        .|. x4 .|. (x4+1))
    mkWord64VerRep4 y1 y2 y3 y4 = mkWord64VerRep (y1+1) (y2+1) (y3+1) (y4+1)

mkVersion (v1:vs)               = PV1 v1 vs


{-# INLINE mkWord64VerRep #-}
mkWord64VerRep :: Int -> Int -> Int -> Int -> Word64
mkWord64VerRep v1 v2 v3 v4 =
      (fromIntegral v1 `shiftL` 48)
  .|. (fromIntegral v2 `shiftL` 32)
  .|. (fromIntegral v3 `shiftL` 16)
  .|.  fromIntegral v4

{-# INLINE inWord16 #-}
inWord16 :: Int -> Bool
inWord16 x = (fromIntegral x :: Word) <= 0xffff
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248

-- | Variant of 'Version' which converts a "Data.Version" 'Version'
-- into Cabal's 'Version' type.
--
-- @since 2.0
mkVersion' :: Base.Version -> Version
mkVersion' = mkVersion . Base.versionBranch

-- | Unpack 'Version' into list of version number components.
--
-- This is the inverse to 'mkVersion', so the following holds:
--
-- > (versionNumbers . mkVersion) vs == vs
--
-- @since 2.0
versionNumbers :: Version -> [Int]
249
250
251
252
253
254
255
256
257
258
259
260
261
versionNumbers (PV1 n ns) = n:ns
versionNumbers (PV0 w)
  | v1 < 0    = []
  | v2 < 0    = [v1]
  | v3 < 0    = [v1,v2]
  | v4 < 0    = [v1,v2,v3]
  | otherwise = [v1,v2,v3,v4]
  where
    v1 = fromIntegral ((w `shiftR` 48) .&. 0xffff) - 1
    v2 = fromIntegral ((w `shiftR` 32) .&. 0xffff) - 1
    v3 = fromIntegral ((w `shiftR` 16) .&. 0xffff) - 1
    v4 = fromIntegral (w .&. 0xffff) - 1

262
263
264
265
266
267
268
269
270
271

-- | Constant representing the special /null/ 'Version'
--
-- The 'nullVersion' compares (via 'Ord') as less than every proper
-- 'Version' value.
--
-- @since 2.0
nullVersion :: Version
-- TODO: at some point, 'mkVersion' may disallow creating /null/
-- 'Version's
272
nullVersion = PV0 0
273
274
275
276
277
278
279
280
281
282
283
284
285

-- | Apply function to list of version number components
--
-- > alterVersion f == mkVersion . f . versionNumbers
--
-- @since 2.0
alterVersion :: ([Int] -> [Int]) -> Version -> Version
alterVersion f = mkVersion . f . versionNumbers

-- internal helper
validVersion :: Version -> Bool
validVersion v = v /= nullVersion && all (>=0) (versionNumbers v)

simonmar's avatar
simonmar committed
286
287
288
289
290
-- -----------------------------------------------------------------------------
-- Version ranges

-- Todo: maybe move this to Distribution.Package.Version?
-- (package-specific versioning scheme).
291
292
293

data VersionRange
  = AnyVersion
294
295
296
  | ThisVersion            Version -- = version
  | LaterVersion           Version -- > version  (NB. not >=)
  | EarlierVersion         Version -- < version
297
  | WildcardVersion        Version -- == ver.*   (same as >= ver && < ver+1)
298
  | MajorBoundVersion      Version -- @^>= ver@ (same as >= ver && < MAJ(ver)+1)
299
300
301
  | UnionVersionRanges     VersionRange VersionRange
  | IntersectVersionRanges VersionRange VersionRange
  | VersionRangeParens     VersionRange -- just '(exp)' parentheses syntax
ttuegel's avatar
ttuegel committed
302
303
304
  deriving (Data, Eq, Generic, Read, Show, Typeable)

instance Binary VersionRange
305

306
307
instance NFData VersionRange where rnf = genericRnf

Mikhail Glushenkov's avatar
Mikhail Glushenkov committed
308
309
310
311
312
313
314
315
316
317
318
319
320
{-# DeprecateD AnyVersion
    "Use 'anyVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
{-# DEPRECATED ThisVersion
    "Use 'thisVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
{-# DEPRECATED LaterVersion
    "Use 'laterVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
{-# DEPRECATED EarlierVersion
    "Use 'earlierVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
{-# DEPRECATED WildcardVersion
    "Use 'anyVersion', 'foldVersionRange' or 'asVersionIntervals'" #-}
{-# DEPRECATED UnionVersionRanges
    "Use 'unionVersionRanges', 'foldVersionRange' or 'asVersionIntervals'" #-}
{-# DEPRECATED IntersectVersionRanges
321
    "Use 'intersectVersionRanges', 'foldVersionRange' or 'asVersionIntervals'"#-}
322

323
324
325
326
327
-- | The version range @-any@. That is, a version range containing all
-- versions.
--
-- > withinRange v anyVersion = True
--
328
329
anyVersion :: VersionRange
anyVersion = AnyVersion
330

331
332
333
334
335
-- | The empty version range, that is a version range containing no versions.
--
-- This can be constructed using any unsatisfiable version range expression,
-- for example @> 1 && < 1@.
--
336
-- > withinRange v noVersion = False
337
--
338
339
noVersion :: VersionRange
noVersion = IntersectVersionRanges (LaterVersion v) (EarlierVersion v)
340
  where v = mkVersion [1]
341

342
343
344
345
-- | The version range @== v@
--
-- > withinRange v' (thisVersion v) = v' == v
--
346
347
348
thisVersion :: Version -> VersionRange
thisVersion = ThisVersion

349
350
351
352
-- | The version range @< v || > v@
--
-- > withinRange v' (notThisVersion v) = v' /= v
--
353
354
355
notThisVersion :: Version -> VersionRange
notThisVersion v = UnionVersionRanges (EarlierVersion v) (LaterVersion v)

356
357
358
359
-- | The version range @> v@
--
-- > withinRange v' (laterVersion v) = v' > v
--
360
361
362
laterVersion :: Version -> VersionRange
laterVersion = LaterVersion

363
364
365
366
-- | The version range @>= v@
--
-- > withinRange v' (orLaterVersion v) = v' >= v
--
ijones's avatar
cleanup    
ijones committed
367
orLaterVersion :: Version -> VersionRange
simonmar's avatar
simonmar committed
368
orLaterVersion   v = UnionVersionRanges (ThisVersion v) (LaterVersion v)
ijones's avatar
cleanup    
ijones committed
369

370
371
372
373
-- | The version range @< v@
--
-- > withinRange v' (earlierVersion v) = v' < v
--
374
375
376
earlierVersion :: Version -> VersionRange
earlierVersion = EarlierVersion

377
378
379
380
-- | The version range @<= v@
--
-- > withinRange v' (orEarlierVersion v) = v' <= v
--
ijones's avatar
cleanup    
ijones committed
381
orEarlierVersion :: Version -> VersionRange
simonmar's avatar
simonmar committed
382
383
orEarlierVersion v = UnionVersionRanges (ThisVersion v) (EarlierVersion v)

384
385
386
387
388
-- | The version range @vr1 || vr2@
--
-- >   withinRange v' (unionVersionRanges vr1 vr2)
-- > = withinRange v' vr1 || withinRange v' vr2
--
389
390
391
unionVersionRanges :: VersionRange -> VersionRange -> VersionRange
unionVersionRanges = UnionVersionRanges

392
393
394
395
396
-- | The version range @vr1 && vr2@
--
-- >   withinRange v' (intersectVersionRanges vr1 vr2)
-- > = withinRange v' vr1 && withinRange v' vr2
--
397
398
intersectVersionRanges :: VersionRange -> VersionRange -> VersionRange
intersectVersionRanges = IntersectVersionRanges
ijones's avatar
cleanup    
ijones committed
399

Simon Jakobi's avatar
Simon Jakobi committed
400
401
402
403
404
405
406
407
408
409
-- | 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)

410
411
412
413
414
415
416
-- | The inverse of a version range
--
-- >   withinRange v' (invertVersionRange vr)
-- > = not (withinRange v' vr)
--
invertVersionRange :: VersionRange -> VersionRange
invertVersionRange =
Mikhail Glushenkov's avatar
Mikhail Glushenkov committed
417
418
    fromVersionIntervals . invertVersionIntervals
    . VersionIntervals . asVersionIntervals
419

420
421
422
423
424
425
426
427
428
-- | The version range @== v.*@.
--
-- For example, for version @1.2@, the version range @== 1.2.*@ is the same as
-- @>= 1.2 && < 1.3@
--
-- > withinRange v' (laterVersion v) = v' >= v && v' < upper v
-- >   where
-- >     upper (Version lower t) = Version (init lower ++ [last lower + 1]) t
--
429
430
431
withinVersion :: Version -> VersionRange
withinVersion = WildcardVersion

432
-- | The version range @^>= v@.
433
--
434
435
436
437
438
439
440
441
442
-- For example, for version @1.2.3.4@, the version range @^>= 1.2.3.4@ is the same as
-- @>= 1.2.3.4 && < 1.3@.
--
-- Note that @^>= 1@ is equivalent to @>= 1 && < 1.1@.
--
-- @since 2.0@
majorBoundVersion :: Version -> VersionRange
majorBoundVersion = MajorBoundVersion

443
444
445
446
447
-- In practice this is not very useful because we normally use inclusive lower
-- bounds and exclusive upper bounds.
--
-- > withinRange v' (laterVersion v) = v' > v
--
ijones's avatar
cleanup    
ijones committed
448
betweenVersionsInclusive :: Version -> Version -> VersionRange
simonmar's avatar
simonmar committed
449
450
betweenVersionsInclusive v1 v2 =
  IntersectVersionRanges (orLaterVersion v1) (orEarlierVersion v2)
451

452
{-# DEPRECATED betweenVersionsInclusive
453
    "In practice this is not very useful because we normally use inclusive lower bounds and exclusive upper bounds" #-}
454

455
456
-- | Given a version range, remove the highest upper bound. Example: @(>= 1 && <
-- 3) || (>= 4 && < 5)@ is converted to @(>= 1 && < 3) || (>= 4)@.
457
458
removeUpperBound :: VersionRange -> VersionRange
removeUpperBound = fromVersionIntervals . relaxLastInterval . toVersionIntervals
459
460
461
462
463
464
465
466
  where
    relaxLastInterval (VersionIntervals intervals) =
      VersionIntervals (relaxLastInterval' intervals)

    relaxLastInterval' []      = []
    relaxLastInterval' [(l,_)] = [(l, NoUpperBound)]
    relaxLastInterval' (i:is)  = i : relaxLastInterval' is

467
468
469
470
471
472
473
474
475
476
477
478
-- | Given a version range, remove the lowest lower bound.
-- Example: @(>= 1 && < 3) || (>= 4 && < 5)@ is converted to
-- @(>= 0 && < 3) || (>= 4 && < 5)@.
removeLowerBound :: VersionRange -> VersionRange
removeLowerBound = fromVersionIntervals . relaxHeadInterval . toVersionIntervals
  where
    relaxHeadInterval (VersionIntervals intervals) =
      VersionIntervals (relaxHeadInterval' intervals)

    relaxHeadInterval' []         = []
    relaxHeadInterval' ((_,u):is) = (minLowerBound,u) : is

479
-- | Fold over the basic syntactic structure of a 'VersionRange'.
480
--
Ian D. Bollinger's avatar
Ian D. Bollinger committed
481
-- This provides a syntactic view of the expression defining the version range.
Duncan Coutts's avatar
Duncan Coutts committed
482
-- The syntactic sugar @\">= v\"@, @\"<= v\"@ and @\"== v.*\"@ is presented
483
484
-- in terms of the other basic syntax.
--
485
486
-- For a semantic view use 'asVersionIntervals'.
--
Duncan Coutts's avatar
Duncan Coutts committed
487
488
489
490
491
492
foldVersionRange :: a                         -- ^ @\"-any\"@ version
                 -> (Version -> a)            -- ^ @\"== v\"@
                 -> (Version -> a)            -- ^ @\"> v\"@
                 -> (Version -> a)            -- ^ @\"< v\"@
                 -> (a -> a -> a)             -- ^ @\"_ || _\"@ union
                 -> (a -> a -> a)             -- ^ @\"_ && _\"@ intersection
493
                 -> VersionRange -> a
494
foldVersionRange anyv this later earlier union intersect = fold
495
496
497
498
499
  where
    fold AnyVersion                     = anyv
    fold (ThisVersion v)                = this v
    fold (LaterVersion v)               = later v
    fold (EarlierVersion v)             = earlier v
500
    fold (WildcardVersion v)            = fold (wildcard v)
501
    fold (MajorBoundVersion v)          = fold (majorBound v)
502
503
    fold (UnionVersionRanges v1 v2)     = union (fold v1) (fold v2)
    fold (IntersectVersionRanges v1 v2) = intersect (fold v1) (fold v2)
504
505
    fold (VersionRangeParens v)         = fold v

506
507
508
509
    wildcard v = intersectVersionRanges
                   (orLaterVersion v)
                   (earlierVersion (wildcardUpperBound v))

510
511
512
513
    majorBound v = intersectVersionRanges
                     (orLaterVersion v)
                     (earlierVersion (majorUpperBound v))

Mikhail Glushenkov's avatar
Mikhail Glushenkov committed
514
515
516
517
-- | An extended variant of 'foldVersionRange' that also provides a view of the
-- expression in which the syntactic sugar @\">= v\"@, @\"<= v\"@ and @\"==
-- v.*\"@ is presented explicitly rather than in terms of the other basic
-- syntax.
518
--
Duncan Coutts's avatar
Duncan Coutts committed
519
520
521
522
523
524
525
foldVersionRange' :: a                         -- ^ @\"-any\"@ version
                  -> (Version -> a)            -- ^ @\"== v\"@
                  -> (Version -> a)            -- ^ @\"> v\"@
                  -> (Version -> a)            -- ^ @\"< v\"@
                  -> (Version -> a)            -- ^ @\">= v\"@
                  -> (Version -> a)            -- ^ @\"<= v\"@
                  -> (Version -> Version -> a) -- ^ @\"== v.*\"@ wildcard. The
526
527
528
529
                                               -- function is passed the
                                               -- inclusive lower bound and the
                                               -- exclusive upper bounds of the
                                               -- range defined by the wildcard.
530
531
532
533
534
535
                  -> (Version -> Version -> a) -- ^ @\"^>= v\"@ major upper bound
                                               -- The function is passed the
                                               -- inclusive lower bound and the
                                               -- exclusive major upper bounds
                                               -- of the range defined by this
                                               -- operator.
Duncan Coutts's avatar
Duncan Coutts committed
536
537
                  -> (a -> a -> a)             -- ^ @\"_ || _\"@ union
                  -> (a -> a -> a)             -- ^ @\"_ && _\"@ intersection
Ian Lynagh's avatar
Ian Lynagh committed
538
                  -> (a -> a)                  -- ^ @\"(_)\"@ parentheses
539
540
                  -> VersionRange -> a
foldVersionRange' anyv this later earlier orLater orEarlier
541
                  wildcard major union intersect parens = fold
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
  where
    fold AnyVersion                     = anyv
    fold (ThisVersion v)                = this v
    fold (LaterVersion v)               = later v
    fold (EarlierVersion v)             = earlier v

    fold (UnionVersionRanges (ThisVersion    v)
                             (LaterVersion   v')) | v==v' = orLater v
    fold (UnionVersionRanges (LaterVersion   v)
                             (ThisVersion    v')) | v==v' = orLater v
    fold (UnionVersionRanges (ThisVersion    v)
                             (EarlierVersion v')) | v==v' = orEarlier v
    fold (UnionVersionRanges (EarlierVersion v)
                             (ThisVersion    v')) | v==v' = orEarlier v

557
    fold (WildcardVersion v)            = wildcard v (wildcardUpperBound v)
558
    fold (MajorBoundVersion v)          = major v (majorUpperBound v)
559
    fold (UnionVersionRanges v1 v2)     = union (fold v1) (fold v2)
560
    fold (IntersectVersionRanges v1 v2) = intersect (fold v1) (fold v2)
561
    fold (VersionRangeParens v)         = parens (fold v)
562

563

564
565
566
567
-- | Does this version fall within the given range?
--
-- This is the evaluation function for the 'VersionRange' type.
--
568
withinRange :: Version -> VersionRange -> Bool
569
570
withinRange v = foldVersionRange
                   True
571
572
573
                   (\v'  -> v == v')
                   (\v'  -> v >  v')
                   (\v'  -> v <  v')
574
575
576
                   (||)
                   (&&)

577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
-- | 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

602
603
604
605
606
607
608
609
610
-- | Does this 'VersionRange' place any restriction on the 'Version' or is it
-- in fact equivalent to 'AnyVersion'.
--
-- Note this is a semantic check, not simply a syntactic check. So for example
-- the following is @True@ (for all @v@).
--
-- > isAnyVersion (EarlierVersion v `UnionVersionRanges` orLaterVersion v)
--
isAnyVersion :: VersionRange -> Bool
611
isAnyVersion vr = case asVersionIntervals vr of
612
613
  [(LowerBound v InclusiveBound, NoUpperBound)] | isVersion0 v -> True
  _                                                            -> False
614
615
616
617
618
619
620
621
622

-- | This is the converse of 'isAnyVersion'. It check if the version range is
-- empty, if there is no possible version that satisfies the version range.
--
-- For example this is @True@ (for all @v@):
--
-- > isNoVersion (EarlierVersion v `IntersectVersionRanges` LaterVersion v)
--
isNoVersion :: VersionRange -> Bool
623
624
625
isNoVersion vr = case asVersionIntervals vr of
  [] -> True
  _  -> False
626
627
628
629
630
631
632

-- | Is this version range in fact just a specific version?
--
-- For example the version range @\">= 3 && <= 3\"@ contains only the version
-- @3@.
--
isSpecificVersion :: VersionRange -> Maybe Version
633
634
635
636
637
isSpecificVersion vr = case asVersionIntervals vr of
  [(LowerBound v  InclusiveBound
   ,UpperBound v' InclusiveBound)]
    | v == v' -> Just v
  _           -> Nothing
638

639
640
641
-- | 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.
642
--
643
644
-- If you need a canonical form use
-- @fromVersionIntervals . toVersionIntervals@
645
--
646
647
648
649
650
651
-- It satisfies the following properties:
--
-- > withinRange v (simplifyVersionRange r) = withinRange v r
--
-- >     withinRange v r = withinRange v r'
-- > ==> simplifyVersionRange r = simplifyVersionRange r'
652
653
-- >  || isNoVersion r
-- >  || isNoVersion r'
654
--
655
simplifyVersionRange :: VersionRange -> VersionRange
656
657
658
659
660
661
662
663
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
  where
    vi = toVersionIntervals vr
664

665
666
667
668
669
----------------------------
-- Wildcard range utilities
--

wildcardUpperBound :: Version -> Version
670
671
wildcardUpperBound = alterVersion $
    \lowerBound -> init lowerBound ++ [last lowerBound + 1]
672

673
isWildcardRange :: Version -> Version -> Bool
674
isWildcardRange ver1 ver2 = check (versionNumbers ver1) (versionNumbers ver2)
675
676
677
678
  where check (n:[]) (m:[]) | n+1 == m = True
        check (n:ns) (m:ms) | n   == m = check ns ms
        check _      _                 = False

679
680
681
682
683
-- | Compute next greater major version to be used as upper bound
--
-- Example: @0.4.1@ produces the version @0.5@ which then can be used
-- to construct a range @>= 0.4.1 && < 0.5@
majorUpperBound :: Version -> Version
684
685
686
687
majorUpperBound = alterVersion $ \numbers -> case numbers of
    []        -> [0,1] -- should not happen
    [m1]      -> [m1,1] -- e.g. version '1'
    (m1:m2:_) -> [m1,m2+1]
688

689
690
691
692
693
------------------
-- Intervals view
--

-- | A complementary representation of a 'VersionRange'. Instead of a boolean
694
695
-- version predicate it uses an increasing sequence of non-overlapping,
-- non-empty intervals.
696
697
698
699
700
701
702
703
704
705
706
707
--
-- 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)

708
709
710
711
712
-- | Inspect the list of version intervals.
--
versionIntervals :: VersionIntervals -> [VersionInterval]
versionIntervals (VersionIntervals is) = is

713
type VersionInterval = (LowerBound, UpperBound)
714
data LowerBound =                LowerBound Version !Bound deriving (Eq, Show)
715
716
717
data UpperBound = NoUpperBound | UpperBound Version !Bound deriving (Eq, Show)
data Bound      = ExclusiveBound | InclusiveBound          deriving (Eq, Show)

718
minLowerBound :: LowerBound
719
minLowerBound = LowerBound (mkVersion [0]) InclusiveBound
720
721

isVersion0 :: Version -> Bool
722
isVersion0 = (== mkVersion [0])
723

724
725
726
727
728
729
730
731
732
733
734
735
736
737
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

738
invariant :: VersionIntervals -> Bool
739
invariant (VersionIntervals intervals) = all validInterval intervals
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
                                      && all doesNotTouch' adjacentIntervals
  where
    doesNotTouch' :: (VersionInterval, VersionInterval) -> Bool
    doesNotTouch' ((_,u), (l',_)) = doesNotTouch u l'

    adjacentIntervals :: [(VersionInterval, VersionInterval)]
    adjacentIntervals
      | null intervals = []
      | otherwise      = zip intervals (tail intervals)

checkInvariant :: VersionIntervals -> VersionIntervals
checkInvariant is = assert (invariant is) is

-- | Directly construct a 'VersionIntervals' from a list of intervals.
--
-- Each interval must be non-empty. The sequence must be in increasing order
Ian D. Bollinger's avatar
Ian D. Bollinger committed
756
-- and no intervals may overlap or touch. If any of these conditions are not
757
758
759
760
761
762
763
-- satisfied the function returns @Nothing@.
--
mkVersionIntervals :: [VersionInterval] -> Maybe VersionIntervals
mkVersionIntervals intervals
  | invariant (VersionIntervals intervals) = Just (VersionIntervals intervals)
  | otherwise                              = Nothing

764
validInterval :: (LowerBound, UpperBound) -> Bool
765
766
767
768
769
770
validInterval i@(l, u) = validLower l && validUpper u && nonEmpty i
  where
    validLower (LowerBound v _) = validVersion v
    validUpper NoUpperBound     = True
    validUpper (UpperBound v _) = validVersion v

771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
-- Check an interval is non-empty
--
nonEmpty :: VersionInterval -> Bool
nonEmpty (_,               NoUpperBound   ) = True
nonEmpty (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))

800
801
-- | Test if a version falls within the version intervals.
--
802
803
-- It exists mostly for completeness and testing. It satisfies the following
-- properties:
804
805
806
807
808
809
810
--
-- > 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
811
812
    withinInterval (lowerBound, upperBound)    = withinLower lowerBound
                                              && withinUpper upperBound
813
814
815
816
    withinLower (LowerBound v' ExclusiveBound) = v' <  v
    withinLower (LowerBound v' InclusiveBound) = v' <= v

    withinUpper NoUpperBound                   = True
817
818
    withinUpper (UpperBound v' ExclusiveBound) = v' >  v
    withinUpper (UpperBound v' InclusiveBound) = v' >= v
819

820
-- | Convert a 'VersionRange' to a sequence of version intervals.
821
822
--
toVersionIntervals :: VersionRange -> VersionIntervals
823
toVersionIntervals = foldVersionRange
824
  (         chkIvl (minLowerBound,               NoUpperBound))
825
826
  (\v    -> chkIvl (LowerBound v InclusiveBound, UpperBound v InclusiveBound))
  (\v    -> chkIvl (LowerBound v ExclusiveBound, NoUpperBound))
827
828
  (\v    -> if isVersion0 v then VersionIntervals [] else
            chkIvl (minLowerBound,               UpperBound v ExclusiveBound))
829
830
  unionVersionIntervals
  intersectVersionIntervals
831
  where
832
    chkIvl interval = checkInvariant (VersionIntervals [interval])
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848

-- | Convert a 'VersionIntervals' value back into a 'VersionRange' expression
-- representing the version intervals.
--
fromVersionIntervals :: VersionIntervals -> VersionRange
fromVersionIntervals (VersionIntervals []) = noVersion
fromVersionIntervals (VersionIntervals intervals) =
    foldr1 UnionVersionRanges [ interval l u | (l, u) <- intervals ]

  where
    interval (LowerBound v  InclusiveBound)
             (UpperBound v' InclusiveBound) | v == v'
                 = ThisVersion v
    interval (LowerBound v  InclusiveBound)
             (UpperBound v' ExclusiveBound) | isWildcardRange v v'
                 = WildcardVersion v
849
    interval l u = lowerBound l `intersectVersionRanges'` upperBound u
850

851
852
853
    lowerBound (LowerBound v InclusiveBound)
                              | isVersion0 v = AnyVersion
                              | otherwise    = orLaterVersion v
854
855
856
857
858
859
    lowerBound (LowerBound v ExclusiveBound) = LaterVersion v

    upperBound NoUpperBound                  = AnyVersion
    upperBound (UpperBound v InclusiveBound) = orEarlierVersion v
    upperBound (UpperBound v ExclusiveBound) = EarlierVersion v

860
861
862
    intersectVersionRanges' vr AnyVersion = vr
    intersectVersionRanges' AnyVersion vr = vr
    intersectVersionRanges' vr vr'        = IntersectVersionRanges vr vr'
863

864
865
866
867
868
869
870
871
872
873
874
875
876
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'

877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
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'

897
898
899
900
901
902
903
904
905
906
907
908
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'
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929

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'

930
931
932
933
934
935
936
invertVersionIntervals :: VersionIntervals
                       -> VersionIntervals
invertVersionIntervals (VersionIntervals xs) =
    case xs of
      -- Empty interval set
      [] -> VersionIntervals [(noLowerBound, NoUpperBound)]
      -- Interval with no lower bound
Mikhail Glushenkov's avatar
Mikhail Glushenkov committed
937
938
      ((lb, ub) : more) | lb == noLowerBound ->
        VersionIntervals $ invertVersionIntervals' ub more
939
940
      -- Interval with a lower bound
      ((lb, ub) : more) ->
Mikhail Glushenkov's avatar
Mikhail Glushenkov committed
941
942
          VersionIntervals $ (noLowerBound, invertLowerBound lb)
          : invertVersionIntervals' ub more
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
    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
969
      noLowerBound = LowerBound (mkVersion [0]) InclusiveBound
970

971
972
973
-------------------------------
-- Parsing and pretty printing
--
simonmar's avatar
simonmar committed
974

975
instance Text VersionRange where
976
977
  disp = fst
       . foldVersionRange'                         -- precedence:
Duncan Coutts's avatar
Duncan Coutts committed
978
           (         Disp.text "-any"                           , 0 :: Int)
979
980
981
982
983
984
           (\v   -> (Disp.text "==" <<>> disp v                   , 0))
           (\v   -> (Disp.char '>'  <<>> disp v                   , 0))
           (\v   -> (Disp.char '<'  <<>> disp v                   , 0))
           (\v   -> (Disp.text ">=" <<>> disp v                   , 0))
           (\v   -> (Disp.text "<=" <<>> disp v                   , 0))
           (\v _ -> (Disp.text "==" <<>> dispWild v               , 0))
985
           (\v _ -> (Disp.text "^>=" <<>> disp v                  , 0))
Mikhail Glushenkov's avatar
Mikhail Glushenkov committed
986
987
988
989
           (\(r1, p1) (r2, p2) ->
             (punct 2 p1 r1 <+> Disp.text "||" <+> punct 2 p2 r2 , 2))
           (\(r1, p1) (r2, p2) ->
             (punct 1 p1 r1 <+> Disp.text "&&" <+> punct 1 p2 r2 , 1))
990
           (\(r, _)   -> (Disp.parens r, 0))
991

992
993
994
    where dispWild ver =
               Disp.hcat (Disp.punctuate (Disp.char '.')
                                         (map Disp.int $ versionNumbers ver))
995
            <<>> Disp.text ".*"
996
997
          punct p p' | p < p'    = Disp.parens
                     | otherwise = id
998

999
  parse = expr
1000
   where
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
        expr   = do Parse.skipSpaces
                    t <- term
                    Parse.skipSpaces
                    (do _  <- Parse.string "||"
                        Parse.skipSpaces
                        e <- expr
                        return (UnionVersionRanges t e)
                     +++
                     return t)
        term   = do f <- factor
                    Parse.skipSpaces
                    (do _  <- Parse.string "&&"
                        Parse.skipSpaces
                        t <- term
                        return (IntersectVersionRanges f t)
                     +++
                     return f)
        factor = Parse.choice $ parens expr
                              : parseAnyVersion
1020
                              : parseNoVersion
1021
1022
                              : parseWildcardRange
                              : map parseRangeOp rangeOps
Duncan Coutts's avatar
Duncan Coutts committed
1023
        parseAnyVersion    = Parse.string "-any" >> return AnyVersion
1024
        parseNoVersion     = Parse.string "-none" >> return noVersion
1025
1026

        parseWildcardRange = do
1027
          _ <- Parse.string "=="
1028
1029
          Parse.skipSpaces
          branch <- Parse.sepBy1 digits (Parse.char '.')
1030
1031
          _ <- Parse.char '.'
          _ <- Parse.char '*'
1032
          return (WildcardVersion (mkVersion branch))
1033

1034
1035
1036
1037
        parens p = Parse.between (Parse.char '(' >> Parse.skipSpaces)
                                 (Parse.char ')' >> Parse.skipSpaces)
                                 (do a <- p
                                     Parse.skipSpaces
1038
                                     return (VersionRangeParens a))
1039

1040
        digits = do
1041
1042
          firstDigit <- Parse.satisfy isDigit
          if firstDigit == '0'
1043
            then return 0
1044
            else do rest <- Parse.munch isDigit
1045
                    return (read (firstDigit : rest)) -- TODO: eradicateNoParse
1046

1047
        parseRangeOp (s,f) = Parse.string s >> Parse.skipSpaces >> fmap f parse
ka2_mail's avatar
ka2_mail committed
1048
        rangeOps = [ ("<",  EarlierVersion),
1049
1050
1051
                     ("<=", orEarlierVersion),
                     (">",  LaterVersion),
                     (">=", orLaterVersion),
1052
                     ("^>=", MajorBoundVersion),
1053
                     ("==", ThisVersion) ]
1054
1055
1056
1057
1058

-- | Does the version range have an upper bound?
--
-- @since 1.24.0.0
hasUpperBound :: VersionRange -> Bool
Mikhail Glushenkov's avatar
Mikhail Glushenkov committed
1059
1060
1061
1062
1063
1064
hasUpperBound = foldVersionRange
                False
                (const True)
                (const False)
                (const True)
                (&&) (||)
1065

Mikhail Glushenkov's avatar
Mikhail Glushenkov committed
1066
1067
1068
1069
-- | Does the version range have an explicit lower bound?
--
-- Note: this function only considers the user-specified lower bounds, but not
-- the implicit >=0 lower bound.
1070
1071
1072
--
-- @since 1.24.0.0
hasLowerBound :: VersionRange -> Bool
Mikhail Glushenkov's avatar
Mikhail Glushenkov committed
1073
1074
1075
1076
1077
1078
hasLowerBound = foldVersionRange
                False
                (const True)
                (const True)
                (const False)
                (&&) (||)