Skip to content

Commit 7d2d3b5

Browse files
authored
Merge pull request #27 from ole/conditional-conformance
Conditional conformance for Equatable (Swift 4.1+) and Hashable (Swift 4.2+)
2 parents 7609357 + eeaf30f commit 7d2d3b5

3 files changed

Lines changed: 81 additions & 32 deletions

File tree

Sources/SortedArray.swift

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public struct SortedArray<Element> {
1919
}
2020

2121
/// Initializes the array with a sequence of unsorted elements and a comparison predicate.
22-
public init<S: Sequence>(unsorted: S, areInIncreasingOrder: @escaping Comparator<Element>) where S.Iterator.Element == Element {
22+
public init<S: Sequence>(unsorted: S, areInIncreasingOrder: @escaping Comparator<Element>) where S.Element == Element {
2323
let sorted = unsorted.sorted(by: areInIncreasingOrder)
2424
self._elements = sorted
2525
self.areInIncreasingOrder = areInIncreasingOrder
@@ -30,7 +30,7 @@ public struct SortedArray<Element> {
3030
/// This is faster than `init(unsorted:areInIncreasingOrder:)` because the elements don't have to sorted again.
3131
///
3232
/// - Precondition: `sorted` is sorted according to the given comparison predicate. If you violate this condition, the behavior is undefined.
33-
public init<S: Sequence>(sorted: S, areInIncreasingOrder: @escaping Comparator<Element>) where S.Iterator.Element == Element {
33+
public init<S: Sequence>(sorted: S, areInIncreasingOrder: @escaping Comparator<Element>) where S.Element == Element {
3434
self._elements = Array(sorted)
3535
self.areInIncreasingOrder = areInIncreasingOrder
3636
}
@@ -55,7 +55,7 @@ public struct SortedArray<Element> {
5555
/// we only need to re-sort once.
5656
///
5757
/// - Complexity: O(_n * log(n)_) where _n_ is the size of the resulting array.
58-
public mutating func insert<S: Sequence>(contentsOf newElements: S) where S.Iterator.Element == Element {
58+
public mutating func insert<S: Sequence>(contentsOf newElements: S) where S.Element == Element {
5959
_elements.append(contentsOf: newElements)
6060
_elements.sort(by: areInIncreasingOrder)
6161
}
@@ -68,7 +68,7 @@ extension SortedArray where Element: Comparable {
6868
}
6969

7070
/// Initializes the array with a sequence of unsorted elements. Uses `<` as the comparison predicate.
71-
public init<S: Sequence>(unsorted: S) where S.Iterator.Element == Element {
71+
public init<S: Sequence>(unsorted: S) where S.Element == Element {
7272
self.init(unsorted: unsorted, areInIncreasingOrder: <)
7373
}
7474

@@ -77,7 +77,7 @@ extension SortedArray where Element: Comparable {
7777
/// This is faster than `init(unsorted:)` because the elements don't have to sorted again.
7878
///
7979
/// - Precondition: `sorted` is sorted according to the `<` predicate. If you violate this condition, the behavior is undefined.
80-
public init<S: Sequence>(sorted: S) where S.Iterator.Element == Element {
80+
public init<S: Sequence>(sorted: S) where S.Element == Element {
8181
self.init(sorted: sorted, areInIncreasingOrder: <)
8282
}
8383
}
@@ -164,25 +164,25 @@ extension SortedArray {
164164
// swift(4.1.50): Swift 4.2 compiler in Swift 4 mode
165165
// swift(4.2): Swift 4.2 compiler
166166
#if !swift(>=4.1.50)
167-
/// Removes the elements in the specified subrange from the array.
168-
///
169-
/// - Parameter bounds: The range of the array to be removed. The
170-
/// bounds of the range must be valid indices of the array.
171-
///
172-
/// - Complexity: O(_n_), where _n_ is the length of the array.
173-
public mutating func removeSubrange(_ bounds: CountableRange<Int>) {
174-
_elements.removeSubrange(bounds)
175-
}
167+
/// Removes the elements in the specified subrange from the array.
168+
///
169+
/// - Parameter bounds: The range of the array to be removed. The
170+
/// bounds of the range must be valid indices of the array.
171+
///
172+
/// - Complexity: O(_n_), where _n_ is the length of the array.
173+
public mutating func removeSubrange(_ bounds: CountableRange<Int>) {
174+
_elements.removeSubrange(bounds)
175+
}
176176

177-
/// Removes the elements in the specified subrange from the array.
178-
///
179-
/// - Parameter bounds: The range of the array to be removed. The
180-
/// bounds of the range must be valid indices of the array.
181-
///
182-
/// - Complexity: O(_n_), where _n_ is the length of the array.
183-
public mutating func removeSubrange(_ bounds: CountableClosedRange<Int>) {
184-
_elements.removeSubrange(bounds)
185-
}
177+
/// Removes the elements in the specified subrange from the array.
178+
///
179+
/// - Parameter bounds: The range of the array to be removed. The
180+
/// bounds of the range must be valid indices of the array.
181+
///
182+
/// - Complexity: O(_n_), where _n_ is the length of the array.
183+
public mutating func removeSubrange(_ bounds: CountableClosedRange<Int>) {
184+
_elements.removeSubrange(bounds)
185+
}
186186
#endif
187187

188188
/// Removes the specified number of elements from the beginning of the
@@ -416,10 +416,27 @@ extension SortedArray {
416416
}
417417
}
418418

419-
public func ==<Element: Equatable> (lhs: SortedArray<Element>, rhs: SortedArray<Element>) -> Bool {
420-
return lhs._elements == rhs._elements
421-
}
419+
#if swift(>=4.1)
420+
extension SortedArray: Equatable where Element: Equatable {
421+
public static func == (lhs: SortedArray<Element>, rhs: SortedArray<Element>) -> Bool {
422+
// Ignore the comparator function for Equatable
423+
return lhs._elements == rhs._elements
424+
}
425+
}
426+
#else
427+
public func ==<Element: Equatable> (lhs: SortedArray<Element>, rhs: SortedArray<Element>) -> Bool {
428+
return lhs._elements == rhs._elements
429+
}
422430

423-
public func !=<Element: Equatable> (lhs: SortedArray<Element>, rhs: SortedArray<Element>) -> Bool {
424-
return lhs._elements != rhs._elements
425-
}
431+
public func !=<Element: Equatable> (lhs: SortedArray<Element>, rhs: SortedArray<Element>) -> Bool {
432+
return lhs._elements != rhs._elements
433+
}
434+
#endif
435+
436+
#if swift(>=4.1.50)
437+
extension SortedArray: Hashable where Element: Hashable {
438+
public func hash(into hasher: inout Hasher) {
439+
hasher.combine(_elements)
440+
}
441+
}
442+
#endif

Tests/UnitTests/SortedArrayTests.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,24 @@ class SortedArrayTests: XCTestCase {
332332
assertElementsEqual(sut, [1,2])
333333
}
334334

335+
func testIsEquatableInSwift4_1AndHigher() {
336+
#if swift(>=4.1)
337+
let array1 = SortedArray(unsorted: [3,2,1])
338+
let array2 = SortedArray(unsorted: 1...3)
339+
XCTAssertEqual(array1, array2)
340+
#endif
341+
}
342+
343+
func testComparatorFunctionIsNotRelevantForEquatable() {
344+
#if swift(>=4.1)
345+
let array1 = SortedArray(unsorted: [1,1,1], areInIncreasingOrder: <)
346+
let array2 = SortedArray(unsorted: [1,1,1], areInIncreasingOrder: >)
347+
let array3 = SortedArray(unsorted: [3,2,1,4])
348+
XCTAssertEqual(array1, array2)
349+
XCTAssertNotEqual(array1, array3)
350+
#endif
351+
}
352+
335353
func testImplementsEqual() {
336354
let sut = SortedArray(unsorted: [3,2,1])
337355
XCTAssertTrue(sut == SortedArray(unsorted: 1...3))
@@ -341,6 +359,16 @@ class SortedArrayTests: XCTestCase {
341359
let sut = SortedArray(unsorted: 1...3)
342360
XCTAssertTrue(sut != SortedArray(unsorted: 1...4))
343361
}
362+
363+
func testIsHashableInSwift4_2AndHigher() {
364+
#if swift(>=4.1.50)
365+
let array1 = SortedArray(unsorted: [3,2,1])
366+
let array2 = SortedArray(unsorted: 1...3)
367+
let array3 = SortedArray(unsorted: [3,2,1,4])
368+
XCTAssertEqual(array1.hashValue, array2.hashValue)
369+
XCTAssertNotEqual(array1.hashValue, array3.hashValue)
370+
#endif
371+
}
344372
}
345373

346374
extension SortedArrayTests {
@@ -398,8 +426,11 @@ extension SortedArrayTests {
398426
("testRemoveElementAtBeginningPreservesSortOrder", testRemoveElementAtBeginningPreservesSortOrder),
399427
("testRemoveElementInMiddlePreservesSortOrder", testRemoveElementInMiddlePreservesSortOrder),
400428
("testRemoveElementAtEndPreservesSortOrder", testRemoveElementAtEndPreservesSortOrder),
429+
("testIsEquatableInSwift4_1AndHigher", testIsEquatableInSwift4_1AndHigher),
430+
("testComparatorFunctionIsNotRelevantForEquatable", testComparatorFunctionIsNotRelevantForEquatable),
401431
("testImplementsEqual", testImplementsEqual),
402432
("testImplementsNotEqual", testImplementsNotEqual),
433+
("testIsHashableInSwift4_2AndHigher", testIsHashableInSwift4_2AndHigher),
403434
]
404435
}
405436
}

Tests/UnitTests/TestHelpers.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import XCTest
22

3+
// FIXME: Can be replaced with XCTAssertEqual when we drop Swift 4.0 compatibility
34
func assertElementsEqual<S1, S2>(_ expression1: @autoclosure () throws -> S1, _ expression2: @autoclosure () throws -> S2, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line)
4-
where S1: Sequence, S2: Sequence, S1.Iterator.Element == S2.Iterator.Element, S1.Iterator.Element: Equatable {
5-
5+
where S1: Sequence, S2: Sequence, S1.Element == S2.Element, S1.Element: Equatable
6+
{
67
// This should give a better error message than using XCTAssert(try expression1().elementsEqual(expression2()), ...)
7-
XCTAssertEqual(Array(try expression1()), Array(try expression2()), message, file: file, line: line)
8+
try XCTAssertEqual(Array(expression1()), Array(expression2()), message, file: file, line: line)
89
}

0 commit comments

Comments
 (0)