Skip to content

Commit 8a6c51f

Browse files
carlobaldassitkelman
authored andcommitted
Slight VersionSet refactoring, test intersect (#20598)
* Enforce VectorSet normalization in constructor (fixes equality testing) * Improve VectorSet normalization code (faster, fewer allocations), * Test VectorSet intersections (including the empty cases which were at the root of issue #20566) * Change VectorSet printing to avoid unicode on Windows
1 parent 769d37b commit 8a6c51f

File tree

2 files changed

+65
-47
lines changed

2 files changed

+65
-47
lines changed

base/pkg/types.jl

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,50 @@ intersect(a::VersionInterval, b::VersionInterval) = VersionInterval(max(a.lower,
2020
==(a::VersionInterval, b::VersionInterval) = a.lower == b.lower && a.upper == b.upper
2121
hash(i::VersionInterval, h::UInt) = hash((i.lower, i.upper), h + (0x0f870a92db508386 % UInt))
2222

23+
function normalize!(ivals::Vector{VersionInterval})
24+
# VersionSet internal normalization:
25+
# removes empty intervals and fuses intervals without gaps
26+
# e.g.:
27+
# [0.0.0,1.0.0) ∪ [1.0.0,1.5.0) ∪ [1.6.0,1.6.0) ∪ [2.0.0,∞)
28+
# becomes:
29+
# [0.0.0,1.5.0) ∪ [2.0.0,∞)
30+
# (still assumes that lower bounds are sorted, and intervals do
31+
# not overlap)
32+
l = length(ivals)
33+
l == 0 && return ivals
34+
35+
lo, up, k0 = ivals[1].lower, ivals[1].upper, 1
36+
fusing = false
37+
for k = 2:l
38+
lo1, up1 = ivals[k].lower, ivals[k].upper
39+
if lo1 == up
40+
up = up1
41+
fusing = true
42+
continue
43+
end
44+
if lo < up
45+
# The only purpose of the "fusing" check is to avoid
46+
# extra allocations
47+
ivals[k0] = fusing ? VersionInterval(lo, up) : ivals[k-1]
48+
k0 += 1
49+
end
50+
fusing = false
51+
lo, up = lo1, up1
52+
end
53+
if lo < up
54+
ivals[k0] = fusing ? VersionInterval(lo, up) : ivals[l]
55+
k0 += 1
56+
end
57+
resize!(ivals, k0 - 1)
58+
return ivals
59+
end
60+
2361
struct VersionSet
2462
intervals::Vector{VersionInterval}
63+
VersionSet(intervals::Vector{VersionInterval}) = new(normalize!(intervals))
64+
# copy is defined inside the struct block to call `new` directly
65+
# without going through `normalize!`
66+
Base.copy(vset::VersionSet) = new(copy(vset.intervals))
2567
end
2668
function VersionSet(versions::Vector{VersionNumber})
2769
intervals = VersionInterval[]
@@ -37,54 +79,25 @@ function VersionSet(versions::Vector{VersionNumber})
3779
end
3880
VersionSet(versions::VersionNumber...) = VersionSet(VersionNumber[versions...])
3981

40-
const empty_versionset = VersionSet([v"0.0",v"0.0"])
82+
const empty_versionset = VersionSet(VersionInterval[])
4183

42-
function normalize!(A::VersionSet)
43-
# removes empty intervals and fuses intervals without gaps
44-
# e.g.:
45-
# [0.0.0,1.0.0) ∪ [1.0.0,1.5.0) ∪ [1.6.0,1.6.0) ∪ [2.0.0,∞)
46-
# becomes:
47-
# [0.0.0,1.5.0) ∪ [2.0.0,∞)
48-
# (still assumes that intervals are properly sorted)
49-
ivals = A.intervals
50-
l = length(ivals)
51-
for k = l:-1:1
52-
isempty(ivals[k]) && (splice!(ivals, k); l -= 1)
53-
end
54-
l > 1 || return A
55-
lo, up, k0 = ivals[l].lower, ivals[l].upper, l
56-
fuse = false
57-
for k = (l-1):-1:1
58-
lo1, up1 = ivals[k].lower, ivals[k].upper
59-
if up1 == lo
60-
fuse = true
61-
lo = lo1
62-
continue
63-
end
64-
fuse && splice!(ivals, (k+1):k0, (VersionInterval(lo, up),))
65-
fuse = false
66-
lo, up = lo1, up1
67-
k0 = k
68-
end
69-
fuse && splice!(ivals, 1:k0, (VersionInterval(lo, up),))
70-
return A
71-
end
72-
73-
show(io::IO, s::VersionSet) = join(io, s.intervals, "")
84+
# Windows console doesn't like Unicode
85+
const _empty_symbol = @static is_windows() ? "empty" : ""
86+
const _union_symbol = @static is_windows() ? " or " : ""
87+
show(io::IO, s::VersionSet) = isempty(s) ? print(io, _empty_symbol) :
88+
join(io, s.intervals, _union_symbol)
7489
isempty(s::VersionSet) = all(isempty, s.intervals)
7590
in(v::VersionNumber, s::VersionSet) = any(i->in(v,i), s.intervals)
7691
function intersect(A::VersionSet, B::VersionSet)
77-
(isempty(A) || isempty(B)) && return normalize!(copy(empty_versionset))
92+
(isempty(A) || isempty(B)) && return copy(empty_versionset)
7893
ivals = [intersect(a,b) for a in A.intervals for b in B.intervals]
79-
filter!(i->!isempty(i), ivals)
8094
sort!(ivals, by=i->i.lower)
8195
VersionSet(ivals)
8296
end
83-
copy(A::VersionSet) = VersionSet(copy(A.intervals))
8497

8598
union(A::VersionSet, B::VersionSet) = union!(copy(A), B)
8699
function union!(A::VersionSet, B::VersionSet)
87-
A == B && return normalize!(A)
100+
A == B && return A
88101
ivals = A.intervals
89102
for intB in B.intervals
90103
lB, uB = intB.lower, intB.upper
@@ -105,7 +118,8 @@ function union!(A::VersionSet, B::VersionSet)
105118
end
106119
end
107120
end
108-
return normalize!(VersionSet(ivals))
121+
normalize!(ivals)
122+
return A
109123
end
110124

111125
==(A::VersionSet, B::VersionSet) = A.intervals == B.intervals
@@ -223,11 +237,7 @@ function _show(io::IO, ritem::ResolveBacktraceItem, indent::String, seen::Set{Re
223237
end
224238
print(io, "required by package $(w[1]), ")
225239
if isa(w[2].versionreq, VersionSet)
226-
if !isempty(w[2].versionreq)
227-
println(io, "whose allowed version range is $(w[2].versionreq):")
228-
else
229-
println(io, "whose allowed version range is empty:")
230-
end
240+
println(io, "whose allowed version range is $(w[2].versionreq):")
231241
else
232242
println(io, "whose only allowed version is $(w[2].versionreq):")
233243
end

test/version.jl

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,16 +255,24 @@ function chkint(a::VersionSet)
255255
return true
256256
end
257257

258-
# VersionSet unions
258+
const empty_versionset = VersionSet(VersionInterval[])
259+
@test isempty(empty_versionset)
260+
261+
# VersionSet intersections and unions
262+
@test empty_versionset empty_versionset == empty_versionset
263+
@test empty_versionset empty_versionset == empty_versionset
259264
for t = 1:1_000
260-
a = VersionSet(map(x->VersionNumber(x, rand(0:3)), sort!(unique(rand(0:8, rand(0:10)))))...)
261-
b = VersionSet(map(x->VersionNumber(x, rand(0:3)), sort!(unique(rand(0:8, rand(0:10)))))...)
265+
a = VersionSet(sort!(map(v->VersionNumber(v...), [(rand(0:8),rand(0:3)) for i = 1:rand(0:10)]))...)
266+
b = VersionSet(sort!(map(v->VersionNumber(v...), [(rand(0:8),rand(0:3)) for i = 1:rand(0:10)]))...)
262267
@assert chkint(a)
263268
@assert chkint(b)
264-
u = union(a, b)
269+
u = a b
265270
@test chkint(u)
271+
i = a b
272+
@test chkint(i)
266273
for vM = 0:9, vm = 0:5
267274
v = VersionNumber(vM, vm)
268275
@test (v a || v b) ? (v u) : (v u)
276+
@test (v a && v b) ? (v i) : (v i)
269277
end
270278
end

0 commit comments

Comments
 (0)