Skip to content

Commit d3c6642

Browse files
authored
Merge pull request #473 from lorentey/cherry-pick-1.2
Cherry pick recent PRs destined for 1.2
2 parents f17ad98 + d5f3b81 commit d3c6642

File tree

11 files changed

+224
-65
lines changed

11 files changed

+224
-65
lines changed

CMakeLists.txt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@ Licensed under Apache License v2.0 with Runtime Library Exception
77
See https://swift.org/LICENSE.txt for license information
88
#]]
99

10-
if(POLICY CMP0091)
11-
cmake_policy(SET CMP0091 NEW)
12-
endif()
13-
1410
cmake_minimum_required(VERSION 3.16)
1511
project(SwiftCollections
1612
LANGUAGES C Swift)
@@ -28,6 +24,8 @@ if(NOT SWIFT_SYSTEM_NAME)
2824
endif()
2925
endif()
3026

27+
include(PlatformInfo)
28+
3129
set(CMAKE_Swift_MODULE_DIRECTORY ${CMAKE_BINARY_DIR}/swift)
3230
set(CMAKE_Swift_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY MultiThreadedDLL)
3331

Sources/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ if(COLLECTIONS_SINGLE_MODULE)
4242
endif()
4343
install(FILES $<TARGET_PROPERTY:${COLLECTIONS_MODULE_NAME},Swift_MODULE_DIRECTORY>/${COLLECTIONS_MODULE_NAME}.swiftdoc
4444
DESTINATION lib/${swift}/${swift_os}/${COLLECTIONS_MODULE_NAME}.swiftmodule
45-
RENAME ${Swift_MODULE_TRIPLE}.swiftdoc)
45+
RENAME ${SwiftCollections_MODULE_TRIPLE}.swiftdoc)
4646
install(FILES $<TARGET_PROPERTY:${COLLECTIONS_MODULE_NAME},Swift_MODULE_DIRECTORY>/${COLLECTIONS_MODULE_NAME}.swiftmodule
4747
DESTINATION lib/${swift}/${swift_os}/${COLLECTIONS_MODULE_NAME}.swiftmodule
48-
RENAME ${Swift_MODULE_TRIPLE}.swiftmodule)
48+
RENAME ${SwiftCollections_MODULE_TRIPLE}.swiftmodule)
4949
else()
5050
_install_target(${COLLECTIONS_MODULE_NAME})
5151
endif()

Sources/HashTreeCollections/HashNode/_HashNode+Storage.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,15 @@ extension _HashNode.Storage {
9292
bytes += itemAlignment - childAlignment
9393
}
9494

95+
let mincap = (bytes &+ childStride &- 1) / childStride
9596
let object = _HashNode.Storage.create(
96-
minimumCapacity: (bytes &+ childStride &- 1) / childStride
97+
minimumCapacity: mincap
9798
) { buffer in
99+
#if os(OpenBSD)
100+
_HashNodeHeader(byteCapacity: mincap * childStride)
101+
#else
98102
_HashNodeHeader(byteCapacity: buffer.capacity * childStride)
103+
#endif
99104
}
100105

101106
object.withUnsafeMutablePointers { header, elements in

Sources/HashTreeCollections/HashNode/_HashTreeStatistics.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,7 @@ extension _HashNode {
118118
// Note: for simplicity, we assume that there is no padding between
119119
// the object header and the storage header.
120120
let start = _getUnsafePointerToStoredProperties(self.raw.storage)
121-
let capacity = self.raw.storage.capacity
122-
let end = $0._memory + capacity * MemoryLayout<_RawHashNode>.stride
121+
let end = $0._memory + $0.byteCapacity
123122
stats.grossBytes += objectHeaderSize + (end - start)
124123

125124
for child in $0.children {

Sources/HeapModule/Heap+UnsafeHandle.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,17 @@ extension Heap._UnsafeHandle {
8181

8282
@inlinable @inline(__always)
8383
internal func minValue(_ a: _HeapNode, _ b: _HeapNode) -> _HeapNode {
84-
self[a] < self[b] ? a : b
84+
// The expression used here matches the implementation of the
85+
// standard `Swift.min(_:_:)` function. This attempts to
86+
// preserve any pre-existing order in case `T` has identity.
87+
// `(min(x, y), max(x, y))` should return `(x, y)` in case `x == y`.
88+
self[b] < self[a] ? b : a
8589
}
8690

8791
@inlinable @inline(__always)
8892
internal func maxValue(_ a: _HeapNode, _ b: _HeapNode) -> _HeapNode {
89-
self[a] < self[b] ? b : a
93+
// In case `a` and `b` match, we need to pick `b`. See `minValue(_:_:)`.
94+
self[b] >= self[a] ? b : a
9095
}
9196
}
9297

Sources/HeapModule/Heap.swift

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ extension Heap {
212212
handle.swapAt(.leftMax, with: &removed)
213213
}
214214
} else {
215-
let maxNode = handle.maxValue(.rightMax, .leftMax)
215+
let maxNode = handle.maxValue(.leftMax, .rightMax)
216216
handle.swapAt(maxNode, with: &removed)
217217
handle.trickleDownMax(maxNode)
218218
}
@@ -268,6 +268,28 @@ extension Heap {
268268
_checkInvariants()
269269
return removed
270270
}
271+
272+
/// Removes all the elements that satisfy the given predicate.
273+
///
274+
/// - Parameter shouldBeRemoved: A closure that takes an element of the
275+
/// heap as its argument and returns a Boolean value indicating
276+
/// whether the element should be removed from the heap.
277+
///
278+
/// - Complexity: O(*n*), where *n* is the number of items in the heap.
279+
@inlinable
280+
public mutating func removeAll(
281+
where shouldBeRemoved: (Element) throws -> Bool
282+
) rethrows {
283+
defer {
284+
if _storage.count > 1 {
285+
_update { handle in
286+
handle.heapify()
287+
}
288+
}
289+
_checkInvariants()
290+
}
291+
try _storage.removeAll(where: shouldBeRemoved)
292+
}
271293

272294
/// Replaces the maximum value in the heap with the given replacement,
273295
/// then updates heap contents to reflect the change.

Sources/OrderedCollections/OrderedSet/OrderedSet+Partial SetAlgebra union.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,28 @@ extension OrderedSet {
8181
result.formUnion(other)
8282
return result
8383
}
84+
85+
/// Returns a new set with the contents of a sequence appended to the end of the set, excluding
86+
/// elements that are already members.
87+
///
88+
/// let a: OrderedSet = [1, 2, 3, 4]
89+
/// let b: OrderedSet = [0, 2, 4, 6]
90+
/// a.union(b) // [1, 2, 3, 4, 0, 6]
91+
///
92+
/// This is functionally equivalent to `self.union(elements)`, but it's
93+
/// more explicit about how the new members are ordered in the new set.
94+
///
95+
/// - Parameter elements: A finite sequence of elements to append.
96+
///
97+
/// - Complexity: Expected to be O(`self.count` + `elements.count`) on average,
98+
/// if `Element` implements high-quality hashing.
99+
@inlinable
100+
public __consuming func appending(
101+
contentsOf elements: __owned some Sequence<Element>
102+
) -> Self {
103+
var result = self
104+
result.append(contentsOf: elements)
105+
return result
106+
}
84107
}
85108

Tests/HeapTests/HeapTests.swift

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -587,4 +587,121 @@ final class HeapTests: CollectionTestCase {
587587
}
588588
}
589589
}
590+
591+
struct Distinguishable: Comparable, CustomStringConvertible {
592+
var value: Int
593+
var id: Int
594+
595+
static func ==(left: Self, right: Self) -> Bool {
596+
left.value == right.value
597+
}
598+
static func <(left: Self, right: Self) -> Bool {
599+
left.value < right.value
600+
}
601+
var description: String { "\(value)/\(id)" }
602+
}
603+
604+
func test_tieBreaks_min() {
605+
var heap: Heap = [
606+
Distinguishable(value: 1, id: 1),
607+
Distinguishable(value: 1, id: 2),
608+
Distinguishable(value: 1, id: 3),
609+
Distinguishable(value: 1, id: 4),
610+
Distinguishable(value: 1, id: 5),
611+
]
612+
while !heap.isEmpty {
613+
let oldID = heap.min!.id
614+
let newID = 10 * oldID
615+
let old = heap.replaceMin(with: Distinguishable(value: 1, id: newID))
616+
expectEqual(old.id, oldID)
617+
expectEqual(heap.min?.id, 10 * oldID)
618+
expectNotNil(heap.removeMin()) { min in
619+
expectEqual(min.id, newID)
620+
}
621+
}
622+
}
623+
624+
func test_tieBreaks_max() {
625+
var heap: Heap = [
626+
Distinguishable(value: 1, id: 1),
627+
Distinguishable(value: 1, id: 2),
628+
Distinguishable(value: 1, id: 3),
629+
Distinguishable(value: 1, id: 4),
630+
Distinguishable(value: 1, id: 5),
631+
]
632+
while !heap.isEmpty {
633+
let oldID = heap.max!.id
634+
let newID = 10 * oldID
635+
print(heap.unordered)
636+
let old = heap.replaceMax(with: Distinguishable(value: 1, id: newID))
637+
expectEqual(old.id, oldID)
638+
expectEqual(heap.max?.id, 10 * oldID)
639+
expectNotNil(heap.removeMax()) { max in
640+
expectEqual(max.id, newID)
641+
}
642+
}
643+
}
644+
645+
func test_removeAll_noneRemoved() {
646+
withEvery("count", in: 0 ..< 20) { count in
647+
withEvery("seed", in: 0 ..< 10) { seed in
648+
var rng = RepeatableRandomNumberGenerator(seed: seed)
649+
let input = (0 ..< count).shuffled(using: &rng)
650+
var heap = Heap(input)
651+
heap.removeAll { _ in false }
652+
let expected = Array(0 ..< count)
653+
expectEqualElements(heap.itemsInAscendingOrder(), expected)
654+
}
655+
}
656+
}
657+
658+
func test_removeAll_allRemoved() {
659+
withEvery("count", in: 0 ..< 20) { count in
660+
withEvery("seed", in: 0 ..< 10) { seed in
661+
var rng = RepeatableRandomNumberGenerator(seed: seed)
662+
let input = (0 ..< count).shuffled(using: &rng)
663+
var heap = Heap(input)
664+
heap.removeAll { _ in true }
665+
expectTrue(heap.isEmpty)
666+
}
667+
}
668+
}
669+
670+
func test_removeAll_removeEvenNumbers() {
671+
withEvery("count", in: 0 ..< 20) { count in
672+
withEvery("seed", in: 0 ..< 10) { seed in
673+
var rng = RepeatableRandomNumberGenerator(seed: seed)
674+
let input = (0 ..< count).shuffled(using: &rng)
675+
var heap = Heap(input)
676+
heap.removeAll { $0 % 2 == 0 }
677+
let expected = Array(stride(from: 1, to: count, by: 2))
678+
expectEqualElements(heap.itemsInAscendingOrder(), expected)
679+
}
680+
}
681+
}
682+
683+
func test_removeAll_throw() throws {
684+
struct DummyError: Error {}
685+
686+
try withEvery("count", in: 1 ..< 20) { count in
687+
try withEvery("seed", in: 0 ..< 10) { seed in
688+
var rng = RepeatableRandomNumberGenerator(seed: seed)
689+
let input = (0 ..< count).shuffled(using: &rng)
690+
var heap = Heap(input)
691+
expectThrows(
692+
try heap.removeAll { v in
693+
if v == count / 2 {
694+
throw DummyError()
695+
}
696+
return v % 2 == 0
697+
}
698+
) { error in
699+
expectTrue(error is DummyError)
700+
}
701+
// Throwing halfway through `removeAll` is expected to reorder items,
702+
// but not remove any.
703+
expectEqualElements(heap.itemsInAscendingOrder(), 0 ..< count)
704+
}
705+
}
706+
}
590707
}

Tests/OrderedCollectionsTests/OrderedSet/OrderedSetTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,20 @@ class OrderedSetTests: CollectionTestCase {
961961
}
962962
}
963963

964+
func test_appending_Self() {
965+
withSampleRanges { r1, r2 in
966+
let expected = Set(r1).union(r2).sorted()
967+
968+
let u1 = OrderedSet(r1)
969+
let u2 = OrderedSet(r2)
970+
let actual1 = u1.appending(contentsOf: u2)
971+
expectEqualElements(actual1, expected)
972+
973+
let actual2 = actual1.appending(contentsOf: u2).appending(contentsOf: u1)
974+
expectEqualElements(actual2, expected)
975+
}
976+
}
977+
964978
func test_formUnion_Self() {
965979
withSampleRanges { r1, r2 in
966980
let expected = Set(r1).union(r2).sorted()

cmake/modules/PlatformInfo.cmake

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#[[
2+
This source file is part of the Swift Collections Open Source Project
3+
4+
Copyright (c) 2025 Apple Inc. and the Swift project authors
5+
Licensed under Apache License v2.0 with Runtime Library Exception
6+
7+
See https://swift.org/LICENSE.txt for license information
8+
#]]
9+
10+
set(target_info_cmd "${CMAKE_Swift_COMPILER}" -print-target-info)
11+
if(CMAKE_Swfit_COMPILER_TARGET)
12+
list(APPEND target_info_cmd -target ${CMAKE_Swift_COMPILER_TARGET})
13+
endif()
14+
execute_process(COMMAND ${target_info_cmd} OUTPUT_VARIABLE target_info_json)
15+
message(CONFIGURE_LOG "Swift target info: ${target_info_cmd}\n"
16+
"${target_info_json}")
17+
18+
if(NOT SwiftCollections_MODULE_TRIPLE)
19+
string(JSON module_triple GET "${target_info_json}" "target" "moduleTriple")
20+
set(SwiftCollections_MODULE_TRIPLE "${module_triple}" CACHE STRING "Triple used for installed swift{doc,module, interface} files")
21+
mark_as_advanced(SwiftCollections_MODULE_TRIPLE)
22+
23+
message(CONFIGURE_LOG "Swift module triple: ${module_triple}")
24+
endif()

0 commit comments

Comments
 (0)