Skip to content

Commit a9ee0b6

Browse files
committed
Add Iterable BorrowIteratorProtocol
1 parent 6dbe0cb commit a9ee0b6

File tree

8 files changed

+289
-16
lines changed

8 files changed

+289
-16
lines changed

Runtimes/Core/cmake/modules/ExperimentalFeatures.cmake

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ add_compile_options(
1313
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature BitwiseCopyable>"
1414
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature Extern>"
1515
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature AllowUnsafeAttribute>"
16-
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature ValueGenerics>")
16+
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature ValueGenerics>"
17+
"$<$<COMPILE_LANGUAGE:Swift>:SHELL:-enable-experimental-feature Lifetimes>")

Runtimes/Core/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ add_library(swiftCore
4242
BidirectionalCollection.swift
4343
Bitset.swift
4444
Bool.swift
45+
BorrowIteratorProtocol.swift
4546
BridgeObjectiveC.swift
4647
BridgeStorage.swift
4748
BridgingBuffer.swift
@@ -93,6 +94,7 @@ add_library(swiftCore
9394
InputStream.swift
9495
IntegerParsing.swift
9596
Integers.swift
97+
Iterable.swift
9698
Join.swift
9799
KeyPath.swift
98100
KeyValuePairs.swift

stdlib/cmake/modules/SwiftSource.cmake

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -342,15 +342,15 @@ function(_add_target_variant_swift_compile_flags
342342
if(SWIFT_STDLIB_OS_VERSIONING)
343343
list(APPEND result "-D" "SWIFT_RUNTIME_OS_VERSIONING")
344344
endif()
345-
345+
346346
if(SWIFT_STDLIB_STATIC_PRINT)
347347
list(APPEND result "-D" "SWIFT_STDLIB_STATIC_PRINT")
348348
endif()
349-
349+
350350
if(SWIFT_STDLIB_ENABLE_UNICODE_DATA)
351351
list(APPEND result "-D" "SWIFT_STDLIB_ENABLE_UNICODE_DATA")
352352
endif()
353-
353+
354354
if(SWIFT_STDLIB_ENABLE_VECTOR_TYPES)
355355
list(APPEND result "-D" "SWIFT_STDLIB_ENABLE_VECTOR_TYPES")
356356
endif()
@@ -612,7 +612,7 @@ function(_compile_swift_files
612612
list(APPEND swift_flags "-autolink-force-load")
613613
endif()
614614

615-
# Don't need to link runtime compatibility libraries for older runtimes
615+
# Don't need to link runtime compatibility libraries for older runtimes
616616
# into the new runtime.
617617
if (SWIFTFILE_IS_STDLIB OR SWIFTFILE_IS_SDK_OVERLAY)
618618
list(APPEND swift_flags "-runtime-compatibility-version" "none")
@@ -640,6 +640,7 @@ function(_compile_swift_files
640640
list(APPEND swift_flags "-enable-experimental-feature" "LifetimeDependence")
641641
list(APPEND swift_flags "-enable-experimental-feature" "InoutLifetimeDependence")
642642
list(APPEND swift_flags "-enable-experimental-feature" "LifetimeDependenceMutableAccessors")
643+
list(APPEND swift_flags "-enable-experimental-feature" "Lifetimes")
643644

644645
list(APPEND swift_flags "-enable-upcoming-feature" "MemberImportVisibility")
645646

stdlib/public/core/Array.swift

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ extension Array: _ArrayProtocol {
498498
@inlinable // FIXME(inline-always)
499499
@inline(__always)
500500
get {
501-
return _buffer.owner
501+
return _buffer.owner
502502
}
503503
}
504504

@@ -814,7 +814,7 @@ extension Array: RandomAccessCollection, MutableCollection {
814814
}
815815
}
816816
}
817-
817+
818818
/// The number of elements in the array.
819819
@inlinable
820820
@_semantics("array.get_count")
@@ -1249,14 +1249,14 @@ extension Array: RangeReplaceableCollection {
12491249
let oldCount = _buffer.mutableCount
12501250
let startNewElements = unsafe _buffer.mutableFirstElementAddress + oldCount
12511251
let buf = unsafe UnsafeMutableBufferPointer(
1252-
start: startNewElements,
1252+
start: startNewElements,
12531253
count: _buffer.mutableCapacity - oldCount)
12541254

12551255
var (remainder,writtenUpTo) = unsafe buf.initialize(from: newElements)
1256-
1256+
12571257
// trap on underflow from the sequence's underestimate:
12581258
let writtenCount = unsafe buf.distance(from: buf.startIndex, to: writtenUpTo)
1259-
_precondition(newElementsCount <= writtenCount,
1259+
_precondition(newElementsCount <= writtenCount,
12601260
"newElements.underestimatedCount was an overestimate")
12611261
// can't check for overflow as sequences can underestimate
12621262

@@ -1440,7 +1440,7 @@ extension Array: RangeReplaceableCollection {
14401440
return try unsafe body(bufferPointer)
14411441
}
14421442
}
1443-
1443+
14441444
@inlinable
14451445
public __consuming func _copyToContiguousArray() -> ContiguousArray<Element> {
14461446
if let n = _buffer.requestNativeBuffer() {
@@ -1799,7 +1799,7 @@ extension Array {
17991799
// a precondition and Array never lies about its count.
18001800
guard var p = buffer.baseAddress
18011801
else { _preconditionFailure("Attempt to copy contents into nil buffer pointer") }
1802-
_precondition(self.count <= buffer.count,
1802+
_precondition(self.count <= buffer.count,
18031803
"Insufficient space allocated to copy array contents")
18041804

18051805
if let s = unsafe _baseAddressIfContiguous {
@@ -2160,3 +2160,25 @@ internal struct _ArrayAnyHashableBox<Element: Hashable>
21602160
}
21612161

21622162
extension Array: @unchecked Sendable where Element: Sendable { }
2163+
2164+
// FIXME: This should be accepted, but we get the following:
2165+
// error: type 'Array<Element>' does not conform to protocol 'Iterable'
2166+
// note: multiple matching functions named '_customContainsEquatableElement' with type '(borrowing Array<Element>.Element) -> Bool?' (aka '(borrowing Element) -> Optional<Bool>')
2167+
2168+
// @available(SwiftStdlib 6.3, *)
2169+
// extension Array: Iterable {
2170+
// public typealias BorrowIterator = Span<Element>.BorrowIterator
2171+
2172+
// @_alwaysEmitIntoClient
2173+
// @_transparent
2174+
// public var estimatedCount: EstimatedCount {
2175+
// .exactly(count)
2176+
// }
2177+
2178+
// @_alwaysEmitIntoClient
2179+
// @_lifetime(borrow self)
2180+
// @_transparent
2181+
// public func startBorrowIteration() -> Span<Element> {
2182+
// self.span
2183+
// }
2184+
// }
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2025 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+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
@available(SwiftStdlib 6.3, *)
14+
public protocol BorrowIteratorProtocol<Element>: ~Copyable, ~Escapable {
15+
associatedtype Element: ~Copyable
16+
17+
// FIXME: This ought to be a core requirement, but `Ref` is not a thing yet.
18+
// @_lifetime(&self)
19+
// @_lifetime(self: copy self)
20+
// mutating func next() -> Ref<Element>?
21+
22+
/// Advance the iterator, returning an ephemeral span over the elements
23+
/// that are ready to be visited.
24+
///
25+
/// If the underlying iterable is a container type, then the returned span
26+
/// typically directly addresses one of its storage buffers. On the other
27+
/// hand, if the underlying iterable materializes its elements on demand,
28+
/// then the returned span addresses some temporary buffer associated with
29+
/// the iterator itself. Consequently, the returned span is tied to this
30+
/// particular invocation of `nextSpan`, and it cannot survive until the next
31+
/// invocation of it.
32+
///
33+
/// If the iterator has not yet reached the end of the underlying iterable,
34+
/// then this method returns a non-empty span of at most `maximumCount`
35+
/// elements, and updates the iterator's current position to the element
36+
/// following the last item in the returned span (or the end, if there is
37+
/// none). The `maximumCount` argument allows callers to avoid getting more
38+
/// items that they are able to process in one go, simplifying usage, and
39+
/// avoiding materializing more elements than needed.
40+
///
41+
/// If the iterator's current position is at the end of the container, then
42+
/// this method returns an empty span without updating the position.
43+
///
44+
/// This method can be used to efficiently process the items of a container
45+
/// in bulk, by directly iterating over its piecewise contiguous pieces of
46+
/// storage:
47+
///
48+
/// var it = items.startBorrowIteration()
49+
/// while true {
50+
/// let span = it.nextSpan(after: &index)
51+
/// if span.isEmpty { break }
52+
/// // Process items in `span`
53+
/// }
54+
///
55+
/// Note: The spans returned by this method are not guaranteed to be disjunct.
56+
/// Iterators that materialize elements on demand typically reuse the same
57+
/// buffer over and over again; and even some proper containers may link to a
58+
/// single storage chunk (or parts of a storage chunk) multiple times, for
59+
/// example to repeat their contents.
60+
///
61+
/// Note: Repeatedly iterating over the same container is expected to return
62+
/// the same items (collected in similarly sized span instances), but the
63+
/// returned spans are not guaranteed to be identical. For example, this is
64+
/// the case with containers that store some of their contents within their
65+
/// direct representation. Such containers may not always have a unique
66+
/// address in memory, and so the locations of the spans exposed by this
67+
/// method may vary between different borrows of the same container.)
68+
@_lifetime(&self)
69+
@_lifetime(self: copy self)
70+
mutating func nextSpan(maximumCount: Int) -> Span<Element>
71+
72+
/// Advance the position of this iterator by the specified offset, or until
73+
/// the end of the underlying iterable.
74+
///
75+
/// Returns the number of items that were skipped. If this is less than
76+
/// `maximumOffset`, then the iterable did not have enough elements left to
77+
/// skip the requested number of items. In this case, the iterator's current
78+
/// position is set to the end of the iterable.
79+
///
80+
/// `maximumOffset` must be nonnegative, unless this is a bidirectional
81+
/// or random-access iterator.
82+
@_lifetime(self: copy self)
83+
mutating func skip(by maximumOffset: Int) -> Int
84+
85+
// FIXME: Add BidirectionalBorrowIteratorProtocol and RandomAccessBorrowIteratorProtocol.
86+
// BidirectionalBorrowIteratorProtocol would need to have a `previousSpan`
87+
// method, which considerably complicates implementation.
88+
// Perhaps these would be better left to as variants of protocol Container,
89+
// which do not need a separate iterator concept.
90+
}
91+
92+
@available(SwiftStdlib 6.3, *)
93+
extension BorrowIteratorProtocol where Self: ~Copyable & ~Escapable {
94+
@_alwaysEmitIntoClient
95+
@_lifetime(&self)
96+
@_lifetime(self: copy self)
97+
@_transparent
98+
public mutating func nextSpan() -> Span<Element> {
99+
nextSpan(maximumCount: Int.max)
100+
}
101+
}
102+
103+
@available(SwiftStdlib 6.3, *)
104+
extension BorrowIteratorProtocol where Self: ~Copyable & ~Escapable {
105+
@_alwaysEmitIntoClient
106+
@_lifetime(self: copy self)
107+
public mutating func skip(by offset: Int) -> Int {
108+
var remainder = offset
109+
while remainder > 0 {
110+
let span = nextSpan(maximumCount: remainder)
111+
if span.isEmpty { break }
112+
remainder &-= span.count
113+
}
114+
return offset &- remainder
115+
}
116+
}
117+
118+
@available(SwiftStdlib 6.3, *)
119+
extension Span: Iterable where Element: ~Copyable {
120+
// FIXME: This simple definition cannot also be a backward (or bidirectional)
121+
// iterator, nor a random-access iterator. If we want to go in that direction,
122+
// we'll need to rather introduce a type more like `RigidArray.BorrowIterator`.
123+
public typealias BorrowIterator = Self
124+
125+
@_alwaysEmitIntoClient
126+
@_transparent
127+
public var estimatedCount: EstimatedCount {
128+
.exactly(count)
129+
}
130+
131+
@_alwaysEmitIntoClient
132+
@_lifetime(copy self)
133+
@_transparent
134+
public func startBorrowIteration() -> Span<Element> {
135+
self
136+
}
137+
}
138+
139+
@available(SwiftStdlib 6.3, *)
140+
extension Span: BorrowIteratorProtocol where Element: ~Copyable {
141+
@_alwaysEmitIntoClient
142+
@_lifetime(&self)
143+
@_lifetime(self: copy self)
144+
public mutating func nextSpan(maximumCount: Int) -> Span<Element> {
145+
let result = extracting(first: maximumCount)
146+
self = extracting(droppingFirst: maximumCount)
147+
return result
148+
}
149+
}
150+
151+
@available(SwiftStdlib 6.3, *)
152+
extension MutableSpan: Iterable where Element: ~Copyable {
153+
public typealias BorrowIterator = Span<Element>.BorrowIterator
154+
155+
@_alwaysEmitIntoClient
156+
@_transparent
157+
public var estimatedCount: EstimatedCount {
158+
.exactly(count)
159+
}
160+
161+
@_alwaysEmitIntoClient
162+
@_lifetime(borrow self)
163+
@_transparent
164+
public func startBorrowIteration() -> Span<Element> {
165+
span
166+
}
167+
}
168+
169+
@available(SwiftStdlib 6.3, *)
170+
extension OutputSpan: Iterable where Element: ~Copyable {
171+
public typealias BorrowIterator = Span<Element>.BorrowIterator
172+
173+
@_alwaysEmitIntoClient
174+
@_transparent
175+
public var estimatedCount: EstimatedCount {
176+
.exactly(count)
177+
}
178+
179+
@_alwaysEmitIntoClient
180+
@_lifetime(borrow self)
181+
@_transparent
182+
public func startBorrowIteration() -> Span<Element> {
183+
self.span
184+
}
185+
}

stdlib/public/core/CMakeLists.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ split_embedded_sources(
4545
EMBEDDED BidirectionalCollection.swift
4646
EMBEDDED Bitset.swift
4747
EMBEDDED Bool.swift
48+
EMBEDDED BorrowIteratorProtocol.swift
4849
NORMAL BridgeObjectiveC.swift
4950
EMBEDDED BridgeStorage.swift
5051
NORMAL BridgingBuffer.swift
@@ -98,6 +99,7 @@ split_embedded_sources(
9899
EMBEDDED InputStream.swift
99100
EMBEDDED IntegerParsing.swift
100101
EMBEDDED Integers.swift
102+
EMBEDDED Iterable.swift
101103
EMBEDDED Join.swift
102104
EMBEDDED KeyPath.swift
103105
EMBEDDED KeyValuePairs.swift
@@ -275,7 +277,7 @@ if(SWIFT_STDLIB_ENABLE_VECTOR_TYPES)
275277
split_embedded_sources(
276278
OUT_LIST_EMBEDDED SWIFTLIB_EMBEDDED_VECTOR_GYB_SOURCES
277279
OUT_LIST_NORMAL SWIFTLIB_VECTOR_GYB_SOURCES
278-
280+
279281
EMBEDDED SIMDIntegerConcreteOperations.swift.gyb
280282
EMBEDDED SIMDFloatConcreteOperations.swift.gyb
281283
EMBEDDED SIMDMaskConcreteOperations.swift.gyb
@@ -530,7 +532,7 @@ if(SWIFT_SHOULD_BUILD_EMBEDDED_STDLIB)
530532
list(GET list 0 arch)
531533
list(GET list 1 mod)
532534
list(GET list 2 triple)
533-
535+
534536
set(SWIFT_SDK_embedded_ARCH_${arch}_MODULE "${mod}")
535537
set(SWIFT_SDK_embedded_LIB_SUBDIR "embedded")
536538
set(SWIFT_SDK_embedded_ARCH_${arch}_TRIPLE "${triple}")

stdlib/public/core/GroupInfo.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@
211211
"RawSpan.swift",
212212
"Span.swift"
213213
],
214-
"UTF8Span": [
214+
"UTF8Span": [
215215
"UTF8EncodingError.swift",
216216
"UTF8Span.swift",
217217
"UTF8SpanBits.swift",
@@ -276,7 +276,9 @@
276276
"EmbeddedStubs.swift",
277277
"EmbeddedPrint.swift",
278278
"InlineArray.swift",
279-
"_InlineArray.swift"
279+
"_InlineArray.swift",
280+
"Iterable.swift",
281+
"BorrowIteratorProtocol.swift"
280282
],
281283
"Result": [
282284
"Result.swift"

0 commit comments

Comments
 (0)