Skip to content

Commit 02177c7

Browse files
committed
[PubGrub] Add support representing disjoint version ranges
This implements support for representing disjoint version ranges so we can properly perform Term intersection where one term is positive and the other one is negative. This will also unblock the infrastructure required for implementing Package.resolved support in PubGrub. <rdar://problem/54280439>
1 parent 6f0ea84 commit 02177c7

File tree

7 files changed

+569
-164
lines changed

7 files changed

+569
-164
lines changed

Sources/PackageGraph/DependencyResolver.swift

Lines changed: 0 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -108,129 +108,6 @@ public enum DependencyResolverError: Error, Equatable, CustomStringConvertible {
108108
}
109109
}
110110

111-
/// An abstract definition for a set of versions.
112-
public enum VersionSetSpecifier: Hashable, CustomStringConvertible {
113-
/// The universal set.
114-
case any
115-
116-
/// The empty set.
117-
case empty
118-
119-
/// A non-empty range of version.
120-
case range(Range<Version>)
121-
122-
/// The exact version that is required.
123-
case exact(Version)
124-
125-
/// Compute the intersection of two set specifiers.
126-
public func intersection(_ rhs: VersionSetSpecifier) -> VersionSetSpecifier {
127-
switch (self, rhs) {
128-
case (.any, _):
129-
return rhs
130-
case (_, .any):
131-
return self
132-
case (.empty, _):
133-
return .empty
134-
case (_, .empty):
135-
return .empty
136-
case (.range(let lhs), .range(let rhs)):
137-
let start = Swift.max(lhs.lowerBound, rhs.lowerBound)
138-
let end = Swift.min(lhs.upperBound, rhs.upperBound)
139-
if start < end {
140-
return .range(start..<end)
141-
} else {
142-
return .empty
143-
}
144-
case (.exact(let v), _):
145-
if rhs.contains(v) {
146-
return self
147-
}
148-
return .empty
149-
case (_, .exact(let v)):
150-
if contains(v) {
151-
return rhs
152-
}
153-
return .empty
154-
}
155-
}
156-
157-
/// Compute the intersection of two set specifiers with differing polarities.
158-
///
159-
/// - Warning: It is assumed that `self` is positive and the passed set
160-
/// specifier `rhs` is negative.
161-
public func intersection(withInverse rhs: VersionSetSpecifier) -> VersionSetSpecifier? {
162-
switch (self, rhs) {
163-
case (_, .any):
164-
return nil
165-
case (.any, _):
166-
assertionFailure("constraints on .any are currently unexpected here")
167-
// FIXME: This is incorrect, we need to return the difference here (.any - rhs),
168-
// which basically means inverse of rhs but we can't return polarity from this
169-
// method yet.
170-
return .any
171-
case (.empty, _), (_, .empty):
172-
assertionFailure("constraints on .empty are currently unexpected here")
173-
// TODO: Check if this is this correct.
174-
return nil
175-
case (.exact(let lhs), .exact(let rhs)):
176-
return lhs == rhs ? nil : self
177-
case (.exact(let exact), .range(let range)), (.range(let range), .exact(let exact)):
178-
if range.contains(version: exact) {
179-
return .range(range.lowerBound..<exact)
180-
}
181-
return nil
182-
case (.range(let lhs), .range(let rhs)):
183-
guard lhs != rhs else {
184-
return nil
185-
}
186-
guard lhs.overlaps(rhs) else {
187-
return .range(lhs)
188-
}
189-
if lhs.lowerBound < rhs.lowerBound {
190-
return .range(lhs.lowerBound..<rhs.lowerBound)
191-
} else {
192-
return .range(rhs.upperBound..<lhs.upperBound)
193-
}
194-
}
195-
}
196-
197-
/// Check if the set contains a version.
198-
public func contains(_ version: Version) -> Bool {
199-
switch self {
200-
case .empty:
201-
return false
202-
case .range(let range):
203-
return range.contains(version: version)
204-
case .any:
205-
return true
206-
case .exact(let v):
207-
return v == version
208-
}
209-
}
210-
211-
public var description: String {
212-
switch self {
213-
case .any:
214-
return "any"
215-
case .empty:
216-
return "empty"
217-
case .range(let range):
218-
var upperBound = range.upperBound
219-
// Patch the version range representation. This shouldn't be
220-
// required once we have custom version range structure.
221-
if upperBound.minor == .max && upperBound.patch == .max {
222-
upperBound = Version(upperBound.major + 1, 0, 0)
223-
}
224-
if upperBound.minor != .max && upperBound.patch == .max {
225-
upperBound = Version(upperBound.major, upperBound.minor + 1, 0)
226-
}
227-
return range.lowerBound.description + "..<" + upperBound.description
228-
case .exact(let version):
229-
return version.description
230-
}
231-
}
232-
}
233-
234111
/// A requirement that a package must satisfy.
235112
public enum PackageRequirement: Hashable {
236113

Sources/PackageGraph/Pubgrub.swift

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -71,22 +71,16 @@ public struct Term: Equatable, Hashable {
7171
let isPositive: Bool
7272
switch (self.isPositive, otherIsPositive) {
7373
case (false, false):
74-
if case .range(let lhs) = lhs, case .range(let rhs) = rhs {
75-
let lower = min(lhs.lowerBound, rhs.lowerBound)
76-
let upper = max(lhs.upperBound, rhs.upperBound)
77-
intersection = .range(lower..<upper)
78-
} else {
79-
intersection = lhs.intersection(rhs)
80-
}
74+
intersection = lhs.union(rhs)
8175
isPositive = false
8276
case (true, true):
8377
intersection = lhs.intersection(rhs)
8478
isPositive = true
8579
case (true, false):
86-
intersection = lhs.intersection(withInverse: rhs)
80+
intersection = lhs.difference(rhs)
8781
isPositive = true
8882
case (false, true):
89-
intersection = rhs.intersection(withInverse: lhs)
83+
intersection = rhs.difference(lhs)
9084
isPositive = true
9185
}
9286

@@ -184,20 +178,7 @@ extension VersionSetSpecifier {
184178
extension Term: CustomStringConvertible {
185179
public var description: String {
186180
let pkg = "\(package)"
187-
var req = ""
188-
189-
let vs = requirement
190-
switch vs {
191-
case .any:
192-
req = "*"
193-
case .empty:
194-
req = "()"
195-
case .exact(let v):
196-
req = v.description
197-
case .range(let range):
198-
req = range.description
199-
}
200-
181+
let req = requirement.description
201182

202183
if !isPositive {
203184
return "¬\(pkg) \(req)"
@@ -224,7 +205,10 @@ public struct Incompatibility: Equatable, Hashable {
224205
}
225206

226207
init(_ terms: OrderedSet<Term>, root: PackageReference, cause: Cause) {
227-
assert(terms.count > 0, "An incompatibility must contain at least one term.")
208+
if terms.isEmpty {
209+
self.init(terms: terms, cause: cause)
210+
return
211+
}
228212

229213
// Remove the root package from generated incompatibilities, since it will
230214
// always be selected.
@@ -1000,7 +984,7 @@ public final class PubgrubDependencyResolver {
1000984
switch assignment.term.requirement {
1001985
case .exact(let version):
1002986
boundVersion = .version(version)
1003-
case .range, .any, .empty:
987+
case .range, .any, .empty, .ranges:
1004988
fatalError("unexpected requirement value for assignment \(assignment.term)")
1005989
}
1006990

@@ -1197,7 +1181,7 @@ public final class PubgrubDependencyResolver {
11971181
/// failed, meaning this incompatibility is either empty or only for the root
11981182
/// package.
11991183
private func isCompleteFailure(_ incompatibility: Incompatibility) -> Bool {
1200-
return incompatibility.terms.count == 1 && incompatibility.terms.first?.package == root
1184+
return incompatibility.terms.isEmpty || (incompatibility.terms.count == 1 && incompatibility.terms.first?.package == root)
12011185
}
12021186

12031187
struct DependencyIncompatibilityError: Swift.Error {
@@ -1230,7 +1214,7 @@ public final class PubgrubDependencyResolver {
12301214

12311215
let requirement: VersionSetSpecifier
12321216
switch pkgTerm.requirement {
1233-
case .any, .empty, .exact:
1217+
case .any, .empty, .exact, .ranges:
12341218
requirement = pkgTerm.requirement
12351219
case .range(let range):
12361220
// FIXME: This isn't really correct. How do we figure out the exact upper bound?
@@ -1353,6 +1337,7 @@ public final class PubgrubDependencyResolver {
13531337
// resolution errors properly. We only end up showing the
13541338
// the problem with the oldest version.
13551339
let nextMajor = Version(version.major + 1, 0, 0)
1340+
// terms.append(Term(container.identifier, .exact(version)))
13561341
terms.append(Term(container.identifier, .range(version..<nextMajor)))
13571342
terms.append(Term(not: dep.identifier, vs))
13581343
return Incompatibility(terms, root: root!, cause: .dependency(package: container.identifier))
@@ -1659,6 +1644,9 @@ final class DiagnosticReportBuilder {
16591644
} else {
16601645
return "\(name) \(range.description)"
16611646
}
1647+
1648+
case .ranges(let ranges):
1649+
return "\(name) \(ranges)"
16621650
}
16631651
}
16641652

@@ -1694,7 +1682,7 @@ extension BoundVersion {
16941682
extension VersionSetSpecifier {
16951683
fileprivate var isExact: Bool {
16961684
switch self {
1697-
case .any, .empty, .range:
1685+
case .any, .empty, .range, .ranges:
16981686
return false
16991687
case .exact:
17001688
return true
@@ -1721,3 +1709,9 @@ extension PackageRequirement {
17211709
}
17221710
}
17231711
}
1712+
1713+
extension Version {
1714+
func nextPatch() -> Version {
1715+
return Version(major, minor, patch + 1)
1716+
}
1717+
}

0 commit comments

Comments
 (0)