Skip to content

Commit 52aca23

Browse files
authored
Merge pull request #603 from lorentey/container-upgrades
Flesh out BorrowingSequence/Container/Producer model a little more
2 parents 19e45ab + ea00086 commit 52aca23

25 files changed

Lines changed: 1640 additions & 298 deletions

Sources/ContainersPreview/CMakeLists.txt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,31 @@ endif()
2727
target_sources(${module_name} PRIVATE
2828
"Extensions/OutputSpan+Extras.swift"
2929
"Extensions/TemporaryAllocation.swift"
30+
"Protocols/Container/BidirectionalContainer.swift"
31+
"Protocols/Container/Container.swift"
32+
"Protocols/Container/ContainerAlgorithms.swift"
33+
"Protocols/Container/DynamicContainer.swift"
34+
"Protocols/Container/MutableContainer.swift"
35+
"Protocols/Container/PermutableContainer.swift"
36+
"Protocols/Container/RandomAccessContainer.swift"
37+
"Protocols/Container/RangeExpression2.swift"
38+
"Protocols/Container/RangeReplaceableContainer.swift"
3039
"Protocols/BorrowingIteratorProtocol.swift"
40+
"Protocols/BorrowingIteratorProtocol+ElementsEqual.swift"
41+
"Protocols/BorrowingIteratorProtocol+Map.swift"
42+
"Protocols/BorrowingIteratorProtocol+Reduce.swift"
43+
"Protocols/BorrowingIteratorProtocol+SpanwiseZip.swift"
3144
"Protocols/BorrowingSequence.swift"
3245
"Protocols/BorrowingSequence+Standard Conformances.swift"
3346
"Protocols/BorrowingSequence+Utilities.swift"
34-
"Protocols/Container.swift"
35-
"Protocols/ContainerAlgorithms.swift"
47+
"Protocols/Drain.swift"
48+
"Protocols/Drain+Map.swift"
49+
"Protocols/Drain+Reduce.swift"
3650
"Protocols/Drain.swift"
3751
"Protocols/Producer.swift"
52+
"Protocols/Producer+Map.swift"
53+
"Protocols/Producer+Reduce.swift"
54+
"Protocols/Producer+Collect.swift"
3855
"Types/Borrow.swift"
3956
"Types/Box.swift"
4057
"Types/Inout.swift"
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Collections open source project
4+
//
5+
// Copyright (c) 2024 - 2026 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#if !COLLECTIONS_SINGLE_MODULE
15+
import InternalCollectionsUtilities
16+
#endif
17+
18+
#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW
19+
20+
@available(SwiftStdlib 5.0, *)
21+
extension BorrowingSequence where Self: ~Copyable & ~Escapable, Element: Equatable {
22+
@inlinable
23+
package func _elementsEqual<
24+
Other: BorrowingSequence<Element> & ~Copyable & ~Escapable
25+
>(
26+
_ other: borrowing Other,
27+
) -> Bool {
28+
let it1 = self.makeBorrowingIterator()
29+
let it2 = other.makeBorrowingIterator()
30+
return it1.elementsEqual(it2)
31+
}
32+
}
33+
34+
@available(SwiftStdlib 5.0, *)
35+
extension BorrowingSequence where Self: ~Copyable & ~Escapable {
36+
/// Returns a Boolean value indicating whether two borrowing sequences contain
37+
/// equivalent elements in the same order, using the given predicate as the
38+
/// equivalence test.
39+
///
40+
/// The predicate must form an *equivalence relation* over the elements. That
41+
/// is, for any elements `a`, `b`, and `c`, the following conditions must
42+
/// hold:
43+
///
44+
/// - `areEquivalent(a, a)` is always `true`. (Reflexivity)
45+
/// - `areEquivalent(a, b)` implies `areEquivalent(b, a)`. (Symmetry)
46+
/// - If `areEquivalent(a, b)` and `areEquivalent(b, c)` are both `true`, then
47+
/// `areEquivalent(a, c)` is also `true`. (Transitivity)
48+
///
49+
/// - Parameters:
50+
/// - other: A BorrowingSequence to compare to this BorrowingSequence.
51+
/// - areEquivalent: A predicate that returns `true` if its two arguments
52+
/// are equivalent; otherwise, `false`.
53+
/// - Returns: `true` if this BorrowingSequence and `other` contain equivalent items,
54+
/// using `areEquivalent` as the equivalence test; otherwise, `false.`
55+
///
56+
/// - Complexity: O(*m*), where *m* is the count of the longer of the input sequences.
57+
@inlinable
58+
package func _elementsEqual<
59+
E: Error,
60+
Other: BorrowingSequence & ~Copyable & ~Escapable
61+
>(
62+
_ other: borrowing Other,
63+
by areEquivalent: (borrowing Element, borrowing Other.Element) throws(E) -> Bool
64+
) throws(E) -> Bool {
65+
let it1 = self.makeBorrowingIterator()
66+
let it2 = other.makeBorrowingIterator()
67+
return try it1.elementsEqual(it2, by: areEquivalent)
68+
}
69+
}
70+
71+
@available(SwiftStdlib 5.0, *)
72+
extension BorrowingIteratorProtocol
73+
where
74+
Self: ~Copyable & ~Escapable,
75+
Element: Equatable
76+
{
77+
@inlinable
78+
package consuming func elementsEqual<
79+
Other: BorrowingIteratorProtocol<Element> & ~Copyable & ~Escapable
80+
>(
81+
_ other: consuming Other,
82+
) -> Bool {
83+
var result = true
84+
_spanwiseZip(state: &result, with: other) { state, a, b in
85+
if a.isEmpty || b.isEmpty {
86+
state = false
87+
return false
88+
}
89+
precondition(a.count == b.count)
90+
for i in 0 ..< a.count {
91+
guard a[unchecked: i] == b[unchecked: i] else {
92+
state = false
93+
return false
94+
}
95+
}
96+
return true
97+
}
98+
return result
99+
}
100+
101+
@inlinable
102+
package consuming func _directElementsEqual<
103+
Other: BorrowingIteratorProtocol<Element> & ~Copyable & ~Escapable
104+
>(
105+
_ other: consuming Other,
106+
) -> Bool {
107+
#if true // FIXME: rdar://150228920 Exclusive access scopes aren't expanded enough
108+
// Note: This is the less efficient implementation of elementsEqual. The
109+
// variant in the #else branch would be preferable, but it doesn't work yet.
110+
// (It lets the two iterators run at their native speeds, with no artificial
111+
// maximumCounts.)
112+
while true {
113+
let a = self.nextSpan()
114+
var i = 0
115+
if a.isEmpty {
116+
return other.nextSpan().isEmpty
117+
}
118+
while i < a.count {
119+
let b = other.nextSpan(maximumCount: a.count - i)
120+
if b.isEmpty {
121+
return false
122+
}
123+
precondition(b.count <= a.count - i)
124+
125+
var j = 0
126+
while j < b.count {
127+
guard a[unchecked: i] == b[unchecked: j] else { return false }
128+
i &+= 1
129+
j &+= 1
130+
}
131+
}
132+
}
133+
#else
134+
var a = Span<Element>()
135+
var b = Span<Element>()
136+
loop:
137+
while true {
138+
if a.isEmpty {
139+
a = self.nextSpan()
140+
}
141+
if b.isEmpty {
142+
b = other.nextSpan()
143+
}
144+
if a.isEmpty || b.isEmpty {
145+
return a.isEmpty && b.isEmpty
146+
}
147+
148+
let c = Swift.min(a.count, b.count)
149+
var i = 0
150+
while i < c {
151+
guard a[unchecked: i] == b[unchecked: i] else { return false }
152+
i &+= 1
153+
}
154+
a = a.extracting(droppingFirst: c)
155+
b = b.extracting(droppingFirst: c)
156+
}
157+
#endif
158+
}
159+
}
160+
161+
@available(SwiftStdlib 5.0, *)
162+
extension BorrowingIteratorProtocol where Self: ~Copyable & ~Escapable {
163+
@inlinable
164+
package consuming func elementsEqual<
165+
E: Error,
166+
Other: BorrowingIteratorProtocol & ~Copyable & ~Escapable
167+
>(
168+
_ other: consuming Other,
169+
by areEquivalent: (borrowing Element, borrowing Other.Element) throws(E) -> Bool
170+
) throws(E) -> Bool {
171+
var result = true
172+
try _spanwiseZip(state: &result, with: other) { state, a, b throws(E) in
173+
assert(a.count == b.count || a.isEmpty || b.isEmpty)
174+
if a.isEmpty || b.isEmpty {
175+
state = false
176+
return false
177+
}
178+
for i in 0 ..< a.count {
179+
guard try areEquivalent(a[i], b[i]) else {
180+
state = false
181+
return false
182+
}
183+
}
184+
return true
185+
}
186+
return result
187+
}
188+
}
189+
190+
#endif
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Collections open source project
4+
//
5+
// Copyright (c) 2026 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW
15+
16+
@available(SwiftStdlib 5.0, *)
17+
extension BorrowingIteratorProtocol where Self: ~Copyable & ~Escapable {
18+
@inlinable
19+
@_lifetime(copy self)
20+
public consuming func map<E: Error, T: ~Copyable>(
21+
_ transform: @escaping (borrowing Element) throws(E) -> T
22+
) throws(E) -> BorrowingMapProducer<Self, T, E> {
23+
BorrowingMapProducer(_base: self, transform: transform)
24+
}
25+
}
26+
27+
@available(SwiftStdlib 5.0, *)
28+
public struct BorrowingMapProducer<
29+
Base: BorrowingIteratorProtocol & ~Copyable & ~Escapable,
30+
Element: ~Copyable,
31+
Error: Swift.Error
32+
>: ~Copyable, ~Escapable {
33+
@_alwaysEmitIntoClient
34+
public let _transform: (borrowing Base.Element) throws(Error) -> Element
35+
36+
@_alwaysEmitIntoClient
37+
public var _it: Base
38+
39+
@inlinable
40+
@_lifetime(copy _base)
41+
internal init(
42+
_base: consuming Base,
43+
transform: @escaping (borrowing Base.Element) throws(Error) -> Element
44+
) {
45+
self._transform = transform
46+
self._it = _base
47+
}
48+
}
49+
50+
// FIXME: Sendable
51+
52+
@available(SwiftStdlib 5.0, *)
53+
extension BorrowingMapProducer: Producer {
54+
public typealias ProducerError = Error
55+
56+
@inlinable
57+
public var underestimatedCount: Int {
58+
0 // FIXME
59+
}
60+
61+
@inlinable
62+
public mutating func next() throws(ProducerError) -> Element? {
63+
let span = _it.nextSpan(maximumCount: 1)
64+
guard !span.isEmpty else { return nil }
65+
return try _transform(span[unchecked: 0])
66+
}
67+
68+
@inlinable
69+
@discardableResult
70+
@_lifetime(target: copy target)
71+
@_lifetime(self: copy self)
72+
public mutating func generate(
73+
into target: inout OutputSpan<Element>
74+
) throws(Error) -> Bool {
75+
var success = false
76+
while !target.isFull {
77+
let span = _it.nextSpan(maximumCount: target.freeCapacity)
78+
guard !span.isEmpty else { break }
79+
success = true
80+
var i = 0
81+
while i < span.count {
82+
try target.append(_transform(span[unchecked: i]))
83+
i &+= 1
84+
}
85+
}
86+
return success
87+
}
88+
}
89+
90+
#endif
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Collections open source project
4+
//
5+
// Copyright (c) 2026 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
// SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#if compiler(>=6.2) && COLLECTIONS_UNSTABLE_CONTAINERS_PREVIEW
15+
16+
@available(SwiftStdlib 5.0, *)
17+
extension BorrowingIteratorProtocol where Self: ~Copyable & ~Escapable {
18+
@inlinable
19+
public consuming func reduce<Result: ~Copyable, E: Error>(
20+
_ initialResult: consuming Result,
21+
_ nextPartialResult: (consuming Result, borrowing Element) throws(E) -> Result
22+
) throws(E) -> Result {
23+
var result = initialResult
24+
while true {
25+
let span = self.nextSpan()
26+
guard !span.isEmpty else { break }
27+
var i = 0
28+
while i < span.count {
29+
result = try nextPartialResult(result, span[unchecked: i])
30+
i &+= 1
31+
}
32+
}
33+
return result
34+
}
35+
36+
@inlinable
37+
public consuming func reduce<Result: ~Copyable, E: Error>(
38+
into initialResult: consuming Result,
39+
_ updateAccumulatingResult: (inout Result, borrowing Element) throws(E) -> Void
40+
) throws(E) -> Result {
41+
var result = initialResult
42+
while true {
43+
let span = self.nextSpan()
44+
guard !span.isEmpty else { break }
45+
var i = 0
46+
while i < span.count {
47+
try updateAccumulatingResult(&result, span[unchecked: i])
48+
i &+= 1
49+
}
50+
}
51+
return result
52+
}
53+
}
54+
55+
#endif

0 commit comments

Comments
 (0)