Skip to content

Commit 96ed023

Browse files
authored
Fix VersionSetSpecifier (#5725)
This fixes two issues: - `difference` had a bug where the difference of 0.x.0..<0.x.1 and 0.x.0 was 0.x.1 instead of `.empty` -- I am actually unsure what the difference of empty and 0.x.0 is supposed to be, but I am keeping it as `.empty` since we have a test for that case - `equals` was using the default implementation, but there are several cases that should be equal but aren't -- one of them came into play in the problem I was looking at, namely a range spanning a single version and an exact version should be considered equal rdar://98452796
1 parent 809b4b5 commit 96ed023

File tree

2 files changed

+85
-2
lines changed

2 files changed

+85
-2
lines changed

Sources/PackageGraph/VersionSetSpecifier.swift

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,57 @@ public enum VersionSetSpecifier: Hashable {
3030
case ranges([Range<Version>])
3131
}
3232

33+
extension VersionSetSpecifier: Equatable {
34+
public static func ==(lhs: VersionSetSpecifier, rhs: VersionSetSpecifier) -> Bool {
35+
switch (lhs, rhs) {
36+
// Basic cases.
37+
case (.any, .any):
38+
return true
39+
case (.empty, .empty):
40+
return true
41+
case (let .range(lhsRange), let .range(rhsRange)):
42+
return lhsRange == rhsRange
43+
case (let .exact(lhsExact), let .exact(rhsExact)):
44+
return lhsExact == rhsExact
45+
case (let .ranges(lhsRanges), let .ranges(rhsRanges)):
46+
return lhsRanges == rhsRanges
47+
48+
// Empty is equivalent to an empty list of ranges or if the list contains one range where the lower bound equals the upper bound.
49+
case (.empty, let .ranges(ranges)):
50+
fallthrough
51+
case (let .ranges(ranges), .empty):
52+
return ranges.isEmpty || (ranges.count == 1 && ranges[0].lowerBound == ranges[0].upperBound)
53+
54+
// Empty is equivalent to a range where the lower bound equals the upper bound.
55+
case (.empty, let .range(range)):
56+
fallthrough
57+
case (let .range(range), .empty):
58+
return range.upperBound == range.lowerBound
59+
60+
// Exact is equal to a range that spans a single patch.
61+
case (let .exact(exact), let .range(range)):
62+
fallthrough
63+
case (let .range(range), let .exact(exact)):
64+
return range.lowerBound == exact && range.upperBound == exact.nextPatch()
65+
66+
// Exact is also equal to a list of ranges with one entry that spans a single patch.
67+
case (let .exact(exact), let .ranges(ranges)):
68+
fallthrough
69+
case (let .ranges(ranges), let .exact(exact)):
70+
return ranges.count == 1 && ranges[0].lowerBound == exact && ranges[0].upperBound == exact.nextPatch()
71+
72+
// A range is equal to a list of ranges with that one range.
73+
case (let .range(range), let .ranges(ranges)):
74+
fallthrough
75+
case (let .ranges(ranges), let .range(range)):
76+
return ranges.count == 1 && ranges[0] == range
77+
78+
default:
79+
return false
80+
}
81+
}
82+
}
83+
3384
extension VersionSetSpecifier {
3485
var isExact: Bool {
3586
switch self {
@@ -225,8 +276,8 @@ extension VersionSetSpecifier {
225276
}
226277

227278
if lhs.lowerBound == rhs {
228-
// Return empty if the range represents the exact version.
229-
if lhs.lowerBound == lhs.upperBound {
279+
// Return empty if the range represents the exact version or the range is empty.
280+
if lhs.lowerBound == lhs.upperBound || lhs.lowerBound.nextPatch() == lhs.upperBound {
230281
return .empty
231282
}
232283
return .range(rhs.nextPatch()..<lhs.upperBound)

Tests/PackageGraphTests/VersionSetSpecifierTests.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ final class VersionSetSpecifierTests: XCTestCase {
5959
XCTAssertEqual(VersionSetSpecifier.range("1.0.0"..<"2.0.0").difference(.exact("1.0.0")), .range("1.0.1"..<"2.0.0"))
6060
XCTAssertEqual(VersionSetSpecifier.range("1.0.0"..<"2.0.0").difference(.exact("1.5.0")), .ranges(["1.0.0"..<"1.5.0", "1.5.1"..<"2.0.0"]))
6161
XCTAssertEqual(VersionSetSpecifier.range("2.0.0"..<"2.0.0").difference(.exact("2.0.0")), .empty)
62+
XCTAssertEqual(VersionSetSpecifier.range("2.0.0"..<"2.0.1").difference(.exact("2.0.0")), .empty)
6263

6364
XCTAssertEqual(VersionSetSpecifier.exact("1.0.0").difference(.range("1.0.0"..<"2.0.0")), .empty)
6465
XCTAssertEqual(VersionSetSpecifier.exact("3.0.0").difference(.range("1.0.0"..<"2.0.0")), .exact("3.0.0"))
@@ -99,4 +100,35 @@ final class VersionSetSpecifierTests: XCTestCase {
99100
XCTAssertEqual(VersionSetSpecifier.ranges(["3.2.0"..<"3.2.3", "3.2.4"..<"4.0.0"]).difference(.exact("3.2.2")), .ranges(["3.2.0"..<"3.2.2", "3.2.4"..<"4.0.0"]))
100101
XCTAssertEqual(VersionSetSpecifier.ranges(["3.2.0"..<"3.2.1", "3.2.3"..<"4.0.0"]).difference(.exact("3.2.0")), .range("3.2.3"..<"4.0.0"))
101102
}
103+
104+
func testEquality() {
105+
// Basic cases.
106+
XCTAssertTrue(VersionSetSpecifier.any == VersionSetSpecifier.any)
107+
XCTAssertTrue(VersionSetSpecifier.empty == VersionSetSpecifier.empty)
108+
XCTAssertTrue(VersionSetSpecifier.range("1.0.0"..<"5.0.0") == VersionSetSpecifier.range("1.0.0"..<"5.0.0"))
109+
XCTAssertTrue(VersionSetSpecifier.exact("1.2.3") == VersionSetSpecifier.exact("1.2.3"))
110+
XCTAssertTrue(VersionSetSpecifier.ranges(["3.2.0"..<"3.2.1", "3.2.3"..<"4.0.0"]) == VersionSetSpecifier.ranges(["3.2.0"..<"3.2.1", "3.2.3"..<"4.0.0"]))
111+
112+
// Empty is equivalent to an empty list of ranges or if the list contains one range where the lower bound equals the upper bound./
113+
XCTAssertTrue(VersionSetSpecifier.empty == VersionSetSpecifier.ranges([]))
114+
XCTAssertTrue(VersionSetSpecifier.ranges([]) == VersionSetSpecifier.empty)
115+
XCTAssertTrue(VersionSetSpecifier.empty == VersionSetSpecifier.ranges(["2.0.0"..<"2.0.0"]))
116+
XCTAssertTrue(VersionSetSpecifier.ranges(["2.0.0"..<"2.0.0"]) == VersionSetSpecifier.empty)
117+
118+
// Empty is equivalent to a range where the lower bound equals the upper bound.
119+
XCTAssertTrue(VersionSetSpecifier.empty == VersionSetSpecifier.range("2.0.0"..<"2.0.0"))
120+
XCTAssertTrue(VersionSetSpecifier.range("2.0.0"..<"2.0.0") == VersionSetSpecifier.empty)
121+
122+
// Exact is equal to a range that spans a single patch.
123+
XCTAssertTrue(VersionSetSpecifier.exact("2.0.1") == VersionSetSpecifier.range("2.0.1"..<"2.0.2"))
124+
XCTAssertTrue(VersionSetSpecifier.range("2.0.1"..<"2.0.2") == VersionSetSpecifier.exact("2.0.1"))
125+
126+
// Exact is also equal to a list of ranges with one entry that spans a single patch.
127+
XCTAssertTrue(VersionSetSpecifier.exact("2.0.1") == VersionSetSpecifier.ranges(["2.0.1"..<"2.0.2"]))
128+
XCTAssertTrue(VersionSetSpecifier.ranges(["2.0.1"..<"2.0.2"]) == VersionSetSpecifier.exact("2.0.1"))
129+
130+
// A range is equal to a list of ranges with that one range.
131+
XCTAssertTrue(VersionSetSpecifier.range("2.0.1"..<"2.0.2") == VersionSetSpecifier.ranges(["2.0.1"..<"2.0.2"]))
132+
XCTAssertTrue(VersionSetSpecifier.ranges(["2.0.1"..<"2.0.2"]) == VersionSetSpecifier.range("2.0.1"..<"2.0.2"))
133+
}
102134
}

0 commit comments

Comments
 (0)