From 5196b1ed74649c99f943fcc9fd8ba2bf4b448655 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Tue, 29 Apr 2025 18:04:39 -0700 Subject: [PATCH 01/10] [SE-0456, -0467] Data+Span+MutableSpan --- CMakeLists.txt | 3 +- Package.swift | 6 +- Sources/FoundationEssentials/Data/Data.swift | 161 +++++++++++++++++- .../FoundationEssentialsTests/DataTests.swift | 134 +++++++++++++++ 4 files changed, 301 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c21d7e3c..8dfb31f0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,7 +111,8 @@ list(APPEND _SwiftFoundation_versions list(APPEND _SwiftFoundation_availability_names "FoundationPreview" "FoundationPredicate" - "FoundationPredicateRegex") + "FoundationPredicateRegex" + "FoundationSpan") # The aligned availability for each name (in the same order) list(APPEND _SwiftFoundation_availability_releases diff --git a/Package.swift b/Package.swift index de020cf03..1d47bca33 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,8 @@ import CompilerPluginSupport let availabilityTags: [_Availability] = [ _Availability("FoundationPreview"), // Default FoundationPreview availability, _Availability("FoundationPredicate"), // Predicate relies on pack parameter runtime support - _Availability("FoundationPredicateRegex") // Predicate regexes rely on new stdlib APIs + _Availability("FoundationPredicateRegex"), // Predicate regexes rely on new stdlib APIs + _Availability("FoundationSpan"), // Availability of Span types ] let versionNumbers = ["0.1", "0.2", "0.3", "0.4", "6.0.2", "6.1", "6.2"] @@ -134,6 +135,9 @@ let package = Package( ] + wasiLibcCSettings, swiftSettings: [ .enableExperimentalFeature("VariadicGenerics"), + .enableExperimentalFeature("LifetimeDependence"), + .enableExperimentalFeature("AddressableTypes"), + .enableExperimentalFeature("BuiltinModule"), .enableExperimentalFeature("AccessLevelOnImport") ] + availabilityMacros + featureSettings, linkerSettings: [ diff --git a/Sources/FoundationEssentials/Data/Data.swift b/Sources/FoundationEssentials/Data/Data.swift index 9f5bb7245..6f56179b9 100644 --- a/Sources/FoundationEssentials/Data/Data.swift +++ b/Sources/FoundationEssentials/Data/Data.swift @@ -49,6 +49,7 @@ #endif internal import _FoundationCShims +import Builtin #if canImport(Darwin) import Darwin @@ -604,6 +605,7 @@ internal final class __DataStorage : @unchecked Sendable { @frozen @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +@_addressableForDependencies public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollection, RangeReplaceableCollection, MutableDataProtocol, ContiguousBytes, Sendable { public typealias Index = Int @@ -2198,7 +2200,105 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> ResultType) rethrows -> ResultType { return try _representation.withUnsafeBytes(body) } - + +#if compiler(>=6.2) + @available(FoundationSpan 6.2, *) + public var bytes: RawSpan { + @lifetime(borrow self) + borrowing get { + let buffer: UnsafeRawBufferPointer + switch _representation { + case .empty: + buffer = UnsafeRawBufferPointer(_empty: ()) + case .inline: + buffer = unsafe UnsafeRawBufferPointer( + start: UnsafeRawPointer(Builtin.addressOfBorrow(self)), + count: _representation.count + ) + case .large(let slice): + buffer = unsafe UnsafeRawBufferPointer( + start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count + ) + case .slice(let slice): + buffer = unsafe UnsafeRawBufferPointer( + start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count + ) + } + let span = unsafe RawSpan(_unsafeBytes: buffer) + return unsafe _overrideLifetime(span, borrowing: self) + } + } + + @available(FoundationSpan 6.2, *) + public var span: Span { + @lifetime(borrow self) + borrowing get { + let span = unsafe bytes._unsafeView(as: UInt8.self) + return _overrideLifetime(span, borrowing: self) + } + } + + @available(FoundationSpan 6.2, *) + public var mutableBytes: MutableRawSpan { + @lifetime(&self) + mutating get { + let buffer: UnsafeMutableRawBufferPointer + switch _representation { + case .empty: + buffer = UnsafeMutableRawBufferPointer(_empty: ()) + case .inline: + buffer = unsafe UnsafeMutableRawBufferPointer( + start: UnsafeMutableRawPointer(Builtin.addressOfBorrow(self)), + count: _representation.count + ) + case .large(let slice): + buffer = unsafe UnsafeMutableRawBufferPointer( + start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count + ) + case .slice(let slice): + buffer = unsafe UnsafeMutableRawBufferPointer( + start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count + ) + } + let span = unsafe MutableRawSpan(_unsafeBytes: buffer) + return unsafe _overrideLifetime(span, mutating: &self) + } + } + + @available(FoundationSpan 6.2, *) + public var mutableSpan: MutableSpan { + @lifetime(&self) + mutating get { +#if false + var bytes = mutableBytes + let span = unsafe bytes._unsafeMutableView(as: UInt8.self) + return _overrideLifetime(span, mutating: &self) +#else + let buffer: UnsafeMutableRawBufferPointer + switch _representation { + case .empty: + buffer = UnsafeMutableRawBufferPointer(_empty: ()) + case .inline: + buffer = unsafe UnsafeMutableRawBufferPointer( + start: UnsafeMutableRawPointer(Builtin.addressOfBorrow(self)), + count: _representation.count + ) + case .large(let slice): + buffer = unsafe UnsafeMutableRawBufferPointer( + start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count + ) + case .slice(let slice): + buffer = unsafe UnsafeMutableRawBufferPointer( + start: slice.storage.mutableBytes?.advanced(by: slice.startIndex), count: slice.count + ) + } + let span = unsafe MutableSpan(_unsafeBytes: buffer) + return unsafe _overrideLifetime(span, mutating: &self) +#endif + } + } +#endif + @_alwaysEmitIntoClient public func withContiguousStorageIfAvailable(_ body: (_ buffer: UnsafeBufferPointer) throws -> ResultType) rethrows -> ResultType? { return try _representation.withUnsafeBytes { @@ -2870,3 +2970,62 @@ extension Data : Codable { } } } + +#if compiler(>=6.2) +// TODO: remove once _overrideLifetime is public in the standard library + +/// Unsafely discard any lifetime dependency on the `dependent` argument. Return +/// a value identical to `dependent` with a lifetime dependency on the caller's +/// borrow scope of the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@lifetime(borrow source) +internal func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, borrowing source: borrowing U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} + +/// Unsafely discard any lifetime dependency on the `dependent` argument. Return +/// a value identical to `dependent` that inherits all lifetime dependencies from +/// the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@lifetime(copy source) +internal func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, copying source: borrowing U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} + +/// Unsafely discard any lifetime dependency on the `dependent` argument. +/// Return a value identical to `dependent` with a lifetime dependency +/// on the caller's exclusive borrow scope of the `source` argument. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@lifetime(&source) +internal func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, + mutating source: inout U +) -> T { + // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence + // should be expressed by a builtin that is hidden within the function body. + dependent +} +#endif diff --git a/Tests/FoundationEssentialsTests/DataTests.swift b/Tests/FoundationEssentialsTests/DataTests.swift index ef820dbab..7f8318aff 100644 --- a/Tests/FoundationEssentialsTests/DataTests.swift +++ b/Tests/FoundationEssentialsTests/DataTests.swift @@ -1634,6 +1634,140 @@ class DataTests : XCTestCase { // source.advanced(by: 5) } + @available(FoundationSpan 6.2, *) + func test_InlineDataSpan() throws { + var source = Data() + var span = source.span + XCTAssertTrue(span.isEmpty) + + source.append(contentsOf: [1, 2, 3]) + span = source.span + XCTAssertFalse(span.isEmpty) + XCTAssertEqual(span.count, source.count) + XCTAssertEqual(span[0], 1) + } + + @available(FoundationSpan 6.2, *) + func test_InlineSliceDataSpan() throws { + let source = Data(0 ... .max) + let span = source.span + XCTAssertEqual(span.count, source.count) + XCTAssertEqual(span[span.indices.last!], .max) + } + + @available(FoundationSpan 6.2, *) + func test_LargeSliceDataSpan() throws { +#if _pointerBitWidth(_64) + let count = Int(Int32.max) +#elseif _pointerBitWidth(_32) + let count = Int(Int16.max) +#else + #error("This test needs updating") +#endif + + let source = Data(repeating: 0, count: count).dropFirst() + XCTAssertNotEqual(source.startIndex, 0) + let span = source.span + XCTAssertFalse(span.isEmpty) + } + + @available(FoundationSpan 6.2, *) + func test_InlineDataMutableSpan() throws { + var source = Data() + var span = source.mutableSpan + XCTAssertTrue(span.isEmpty) + + source.append(contentsOf: [1, 2, 3]) + let count = source.count + span = source.mutableSpan + let i = try XCTUnwrap(span.indices.randomElement()) + XCTAssertFalse(span.isEmpty) + XCTAssertEqual(span.count, count) + let v = UInt8.random(in: 10..<100) + span[i] = v + XCTAssertEqual(source[i], v) + } + + @available(FoundationSpan 6.2, *) + func test_InlineSliceDataMutableSpan() throws { + var source = Data(0..<100) + let count = source.count + var span = source.mutableSpan + XCTAssertEqual(span.count, count) + let i = try XCTUnwrap(span.indices.randomElement()) + span[i] = .max + XCTAssertEqual(source[i], .max) + } + + @available(FoundationSpan 6.2, *) + func test_LargeSliceDataMutableSpan() throws { + #if _pointerBitWidth(_64) + var count = Int(Int32.max) + #elseif _pointerBitWidth(_32) + var count = Int(Int16.max) + #else + #error("This test needs updating") + #endif + + var source = Data(repeating: 0, count: count).dropFirst() + XCTAssertNotEqual(source.startIndex, 0) + count = source.count + var span = source.mutableSpan + XCTAssertEqual(span.count, count) + let i = try XCTUnwrap(span.indices.dropFirst().randomElement()) + span[i] = .max + XCTAssertEqual(source[i], 0) + XCTAssertEqual(source[i+1], .max) + } + + @available(FoundationSpan 6.2, *) + func test_InlineDataMutableRawSpan() throws { + var source = Data() + var span = source.mutableBytes + XCTAssertTrue(span.isEmpty) + + source.append(contentsOf: [1, 2, 3]) + let count = source.count + span = source.mutableBytes + let i = try XCTUnwrap(span.byteOffsets.randomElement()) + XCTAssertFalse(span.isEmpty) + XCTAssertEqual(span.byteCount, count) + let v = UInt8.random(in: 10..<100) + span.storeBytes(of: v, toByteOffset: i, as: UInt8.self) + XCTAssertEqual(source[i], v) + } + + @available(FoundationSpan 6.2, *) + func test_InlineSliceDataMutableRawSpan() throws { + var source = Data(0..<100) + let count = source.count + var span = source.mutableBytes + XCTAssertEqual(span.byteCount, count) + let i = try XCTUnwrap(span.byteOffsets.randomElement()) + span.storeBytes(of: -1, toByteOffset: i, as: Int8.self) + XCTAssertEqual(source[i], .max) + } + + @available(FoundationSpan 6.2, *) + func test_LargeSliceDataMutableRawSpan() throws { +#if _pointerBitWidth(_64) + var count = Int(Int32.max) +#elseif _pointerBitWidth(_32) + var count = Int(Int16.max) +#else + #error("This test needs updating") +#endif + + var source = Data(repeating: 0, count: count).dropFirst() + XCTAssertNotEqual(source.startIndex, 0) + count = source.count + var span = source.mutableBytes + XCTAssertEqual(span.byteCount, count) + let i = try XCTUnwrap(span.byteOffsets.dropFirst().randomElement()) + span.storeBytes(of: -1, toByteOffset: i, as: Int8.self) + XCTAssertEqual(source[i], 0) + XCTAssertEqual(source[i+1], .max) + } #if false // FIXME: XCTest doesn't support crash tests yet rdar://20195010&22387653 func test_bounding_failure_subdata() { From 9857ce13f39623ebfca3b13d2bd49331c1958be1 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 30 Apr 2025 14:51:14 -0700 Subject: [PATCH 02/10] Document compiler issue --- Sources/FoundationEssentials/Data/Data.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/FoundationEssentials/Data/Data.swift b/Sources/FoundationEssentials/Data/Data.swift index 6f56179b9..32ca407e4 100644 --- a/Sources/FoundationEssentials/Data/Data.swift +++ b/Sources/FoundationEssentials/Data/Data.swift @@ -2269,7 +2269,7 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect public var mutableSpan: MutableSpan { @lifetime(&self) mutating get { -#if false +#if false // see https://github.com/swiftlang/swift/issues/81218 var bytes = mutableBytes let span = unsafe bytes._unsafeMutableView(as: UInt8.self) return _overrideLifetime(span, mutating: &self) From efd6b6c23b07808b6320a497ac085d9feb3a7f52 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Wed, 30 Apr 2025 14:51:41 -0700 Subject: [PATCH 03/10] fix use of non-public initializers --- Sources/FoundationEssentials/Data/Data.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/FoundationEssentials/Data/Data.swift b/Sources/FoundationEssentials/Data/Data.swift index 32ca407e4..65ff69721 100644 --- a/Sources/FoundationEssentials/Data/Data.swift +++ b/Sources/FoundationEssentials/Data/Data.swift @@ -2209,7 +2209,7 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect let buffer: UnsafeRawBufferPointer switch _representation { case .empty: - buffer = UnsafeRawBufferPointer(_empty: ()) + buffer = UnsafeRawBufferPointer(start: nil, count: 0) case .inline: buffer = unsafe UnsafeRawBufferPointer( start: UnsafeRawPointer(Builtin.addressOfBorrow(self)), @@ -2245,7 +2245,7 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect let buffer: UnsafeMutableRawBufferPointer switch _representation { case .empty: - buffer = UnsafeMutableRawBufferPointer(_empty: ()) + buffer = UnsafeMutableRawBufferPointer(start: nil, count: 0) case .inline: buffer = unsafe UnsafeMutableRawBufferPointer( start: UnsafeMutableRawPointer(Builtin.addressOfBorrow(self)), @@ -2277,7 +2277,7 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect let buffer: UnsafeMutableRawBufferPointer switch _representation { case .empty: - buffer = UnsafeMutableRawBufferPointer(_empty: ()) + buffer = UnsafeMutableRawBufferPointer(start: nil, count: 0) case .inline: buffer = unsafe UnsafeMutableRawBufferPointer( start: UnsafeMutableRawPointer(Builtin.addressOfBorrow(self)), From 8a6a7d998825228868665e8539c8c0b5ec376d93 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 23 May 2025 12:30:55 -0700 Subject: [PATCH 04/10] Address availability and compiler versions --- Package.swift | 2 +- README.md | 2 +- Sources/FoundationEssentials/Data/Data.swift | 6 ++-- .../FoundationEssentialsTests/DataTests.swift | 35 ++++++++++++------- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/Package.swift b/Package.swift index 1d47bca33..f23d69e3b 100644 --- a/Package.swift +++ b/Package.swift @@ -10,7 +10,7 @@ let availabilityTags: [_Availability] = [ _Availability("FoundationPreview"), // Default FoundationPreview availability, _Availability("FoundationPredicate"), // Predicate relies on pack parameter runtime support _Availability("FoundationPredicateRegex"), // Predicate regexes rely on new stdlib APIs - _Availability("FoundationSpan"), // Availability of Span types + _Availability("FoundationSpan", availability: .future), // Availability of Span types ] let versionNumbers = ["0.1", "0.2", "0.3", "0.4", "6.0.2", "6.1", "6.2"] diff --git a/README.md b/README.md index 4cc54f77b..d871bf60b 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ On all other Swift platforms, `swift-foundation` is available as part of the too ## Building and Testing > [!NOTE] -> Building swift-foundation requires the in-development Swift 6.0 toolchain. You can download the Swift 6.0 nightly toolchain from [the Swift website](https://swift.org/install). +> Building swift-foundation requires the in-development Swift 6.2 toolchain. You can download the Swift 6.2 nightly toolchain from [the Swift website](https://swift.org/install). Before building Foundation, first ensure that you have a Swift toolchain installed. Next, check out the _Getting Started_ section of the [Foundation Build Process](Foundation_Build_Process.md#getting-started) guide for detailed steps on building and testing. diff --git a/Sources/FoundationEssentials/Data/Data.swift b/Sources/FoundationEssentials/Data/Data.swift index 65ff69721..efd1f5af2 100644 --- a/Sources/FoundationEssentials/Data/Data.swift +++ b/Sources/FoundationEssentials/Data/Data.swift @@ -605,7 +605,9 @@ internal final class __DataStorage : @unchecked Sendable { @frozen @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +#if compiler(>=6.2) @_addressableForDependencies +#endif public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollection, RangeReplaceableCollection, MutableDataProtocol, ContiguousBytes, Sendable { public typealias Index = Int @@ -2201,7 +2203,6 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect return try _representation.withUnsafeBytes(body) } -#if compiler(>=6.2) @available(FoundationSpan 6.2, *) public var bytes: RawSpan { @lifetime(borrow self) @@ -2297,7 +2298,6 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect #endif } } -#endif @_alwaysEmitIntoClient public func withContiguousStorageIfAvailable(_ body: (_ buffer: UnsafeBufferPointer) throws -> ResultType) rethrows -> ResultType? { @@ -2971,7 +2971,6 @@ extension Data : Codable { } } -#if compiler(>=6.2) // TODO: remove once _overrideLifetime is public in the standard library /// Unsafely discard any lifetime dependency on the `dependent` argument. Return @@ -3028,4 +3027,3 @@ internal func _overrideLifetime< // should be expressed by a builtin that is hidden within the function body. dependent } -#endif diff --git a/Tests/FoundationEssentialsTests/DataTests.swift b/Tests/FoundationEssentialsTests/DataTests.swift index 7f8318aff..27b0f83bc 100644 --- a/Tests/FoundationEssentialsTests/DataTests.swift +++ b/Tests/FoundationEssentialsTests/DataTests.swift @@ -1634,8 +1634,9 @@ class DataTests : XCTestCase { // source.advanced(by: 5) } - @available(FoundationSpan 6.2, *) func test_InlineDataSpan() throws { + guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } + var source = Data() var span = source.span XCTAssertTrue(span.isEmpty) @@ -1647,16 +1648,18 @@ class DataTests : XCTestCase { XCTAssertEqual(span[0], 1) } - @available(FoundationSpan 6.2, *) func test_InlineSliceDataSpan() throws { + guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } + let source = Data(0 ... .max) let span = source.span XCTAssertEqual(span.count, source.count) XCTAssertEqual(span[span.indices.last!], .max) } - @available(FoundationSpan 6.2, *) func test_LargeSliceDataSpan() throws { + guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } + #if _pointerBitWidth(_64) let count = Int(Int32.max) #elseif _pointerBitWidth(_32) @@ -1671,8 +1674,9 @@ class DataTests : XCTestCase { XCTAssertFalse(span.isEmpty) } - @available(FoundationSpan 6.2, *) func test_InlineDataMutableSpan() throws { + guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } + var source = Data() var span = source.mutableSpan XCTAssertTrue(span.isEmpty) @@ -1688,8 +1692,9 @@ class DataTests : XCTestCase { XCTAssertEqual(source[i], v) } - @available(FoundationSpan 6.2, *) func test_InlineSliceDataMutableSpan() throws { + guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } + var source = Data(0..<100) let count = source.count var span = source.mutableSpan @@ -1699,15 +1704,16 @@ class DataTests : XCTestCase { XCTAssertEqual(source[i], .max) } - @available(FoundationSpan 6.2, *) func test_LargeSliceDataMutableSpan() throws { - #if _pointerBitWidth(_64) + guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } + +#if _pointerBitWidth(_64) var count = Int(Int32.max) - #elseif _pointerBitWidth(_32) +#elseif _pointerBitWidth(_32) var count = Int(Int16.max) - #else +#else #error("This test needs updating") - #endif +#endif var source = Data(repeating: 0, count: count).dropFirst() XCTAssertNotEqual(source.startIndex, 0) @@ -1720,8 +1726,9 @@ class DataTests : XCTestCase { XCTAssertEqual(source[i+1], .max) } - @available(FoundationSpan 6.2, *) func test_InlineDataMutableRawSpan() throws { + guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } + var source = Data() var span = source.mutableBytes XCTAssertTrue(span.isEmpty) @@ -1737,8 +1744,9 @@ class DataTests : XCTestCase { XCTAssertEqual(source[i], v) } - @available(FoundationSpan 6.2, *) func test_InlineSliceDataMutableRawSpan() throws { + guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } + var source = Data(0..<100) let count = source.count var span = source.mutableBytes @@ -1748,8 +1756,9 @@ class DataTests : XCTestCase { XCTAssertEqual(source[i], .max) } - @available(FoundationSpan 6.2, *) func test_LargeSliceDataMutableRawSpan() throws { + guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } + #if _pointerBitWidth(_64) var count = Int(Int32.max) #elseif _pointerBitWidth(_32) From a02e0b7f57c8d7c849968ee6c6f793212b9ae48a Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 23 May 2025 17:02:39 -0700 Subject: [PATCH 05/10] adjust cmake configuration --- CMakeLists.txt | 3 ++- Sources/FoundationEssentials/CMakeLists.txt | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8dfb31f0a..12eb7612d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,7 +118,8 @@ list(APPEND _SwiftFoundation_availability_names list(APPEND _SwiftFoundation_availability_releases ${_SwiftFoundation_BaseAvailability} ${_SwiftFoundation_BaseAvailability} - ${_SwiftFoundation_BaseAvailability}) + ${_SwiftFoundation_BaseAvailability} + ${_SwiftFoundation_FutureAvailability}) foreach(version ${_SwiftFoundation_versions}) foreach(name release IN ZIP_LISTS _SwiftFoundation_availability_names _SwiftFoundation_availability_releases) diff --git a/Sources/FoundationEssentials/CMakeLists.txt b/Sources/FoundationEssentials/CMakeLists.txt index afdbdd507..b88ee86d7 100644 --- a/Sources/FoundationEssentials/CMakeLists.txt +++ b/Sources/FoundationEssentials/CMakeLists.txt @@ -80,6 +80,9 @@ endif() target_compile_options(FoundationEssentials PRIVATE "SHELL:$<$:-Xfrontend -enable-experimental-feature -Xfrontend VariadicGenerics>" + "SHELL:$<$:-Xfrontend -enable-experimental-feature -Xfrontend LifetimeDependence>" + "SHELL:$<$:-Xfrontend -enable-experimental-feature -Xfrontend AddressableTypes>" + "SHELL:$<$:-Xfrontend -enable-experimental-feature -Xfrontend BuiltinModule>" "SHELL:$<$:-Xfrontend -enable-experimental-feature -Xfrontend AccessLevelOnImport>" "SHELL:$<$:-Xfrontend -enable-experimental-feature -Xfrontend StrictConcurrency>" "SHELL:$<$:-Xfrontend -enable-upcoming-feature -Xfrontend InferSendableFromCaptures>" From 88e1cbdc3ebbda2baedbaeedad1d8a2dfea44187 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 30 May 2025 11:15:40 -0700 Subject: [PATCH 06/10] temporarily disable Span and MutableSpan for Windows The current Windows nightly is too old to be able to compile the expected syntax. --- Package.swift | 19 ++++++++++++++++++- Sources/FoundationEssentials/Data/Data.swift | 10 ++++------ .../FoundationEssentialsTests/DataTests.swift | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/Package.swift b/Package.swift index f23d69e3b..744a79207 100644 --- a/Package.swift +++ b/Package.swift @@ -136,6 +136,14 @@ let package = Package( swiftSettings: [ .enableExperimentalFeature("VariadicGenerics"), .enableExperimentalFeature("LifetimeDependence"), + .enableExperimentalFeature( + "InoutLifetimeDependence", + .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .linux]) + ), + .enableExperimentalFeature( + "LifetimeDependenceMutableAccessors", + .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .linux]) + ), .enableExperimentalFeature("AddressableTypes"), .enableExperimentalFeature("BuiltinModule"), .enableExperimentalFeature("AccessLevelOnImport") @@ -153,7 +161,16 @@ let package = Package( resources: [ .copy("Resources") ], - swiftSettings: availabilityMacros + featureSettings + swiftSettings: [ + .enableExperimentalFeature( + "InoutLifetimeDependence", + .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .linux]) + ), + .enableExperimentalFeature( + "LifetimeDependenceMutableAccessors", + .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .linux]) + ), + ] + availabilityMacros + featureSettings ), // FoundationInternationalization diff --git a/Sources/FoundationEssentials/Data/Data.swift b/Sources/FoundationEssentials/Data/Data.swift index efd1f5af2..dca8ca372 100644 --- a/Sources/FoundationEssentials/Data/Data.swift +++ b/Sources/FoundationEssentials/Data/Data.swift @@ -2239,6 +2239,7 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect } } +#if compiler(>=5.9) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors @available(FoundationSpan 6.2, *) public var mutableBytes: MutableRawSpan { @lifetime(&self) @@ -2298,6 +2299,7 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect #endif } } +#endif // $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors @_alwaysEmitIntoClient public func withContiguousStorageIfAvailable(_ body: (_ buffer: UnsafeBufferPointer) throws -> ResultType) rethrows -> ResultType? { @@ -2986,8 +2988,6 @@ internal func _overrideLifetime< >( _ dependent: consuming T, borrowing source: borrowing U ) -> T { - // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence - // should be expressed by a builtin that is hidden within the function body. dependent } @@ -3004,11 +3004,10 @@ internal func _overrideLifetime< >( _ dependent: consuming T, copying source: borrowing U ) -> T { - // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence - // should be expressed by a builtin that is hidden within the function body. dependent } +#if compiler(>=5.9) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors /// Unsafely discard any lifetime dependency on the `dependent` argument. /// Return a value identical to `dependent` with a lifetime dependency /// on the caller's exclusive borrow scope of the `source` argument. @@ -3023,7 +3022,6 @@ internal func _overrideLifetime< _ dependent: consuming T, mutating source: inout U ) -> T { - // TODO: Remove @_unsafeNonescapableResult. Instead, the unsafe dependence - // should be expressed by a builtin that is hidden within the function body. dependent } +#endif // $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors diff --git a/Tests/FoundationEssentialsTests/DataTests.swift b/Tests/FoundationEssentialsTests/DataTests.swift index 27b0f83bc..0e73ad667 100644 --- a/Tests/FoundationEssentialsTests/DataTests.swift +++ b/Tests/FoundationEssentialsTests/DataTests.swift @@ -1637,6 +1637,7 @@ class DataTests : XCTestCase { func test_InlineDataSpan() throws { guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } +#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data() var span = source.span XCTAssertTrue(span.isEmpty) @@ -1646,15 +1647,18 @@ class DataTests : XCTestCase { XCTAssertFalse(span.isEmpty) XCTAssertEqual(span.count, source.count) XCTAssertEqual(span[0], 1) +#endif } func test_InlineSliceDataSpan() throws { guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } +#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors let source = Data(0 ... .max) let span = source.span XCTAssertEqual(span.count, source.count) XCTAssertEqual(span[span.indices.last!], .max) +#endif } func test_LargeSliceDataSpan() throws { @@ -1668,15 +1672,18 @@ class DataTests : XCTestCase { #error("This test needs updating") #endif +#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors let source = Data(repeating: 0, count: count).dropFirst() XCTAssertNotEqual(source.startIndex, 0) let span = source.span XCTAssertFalse(span.isEmpty) +#endif } func test_InlineDataMutableSpan() throws { guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } +#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data() var span = source.mutableSpan XCTAssertTrue(span.isEmpty) @@ -1690,11 +1697,13 @@ class DataTests : XCTestCase { let v = UInt8.random(in: 10..<100) span[i] = v XCTAssertEqual(source[i], v) +#endif } func test_InlineSliceDataMutableSpan() throws { guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } +#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data(0..<100) let count = source.count var span = source.mutableSpan @@ -1702,6 +1711,7 @@ class DataTests : XCTestCase { let i = try XCTUnwrap(span.indices.randomElement()) span[i] = .max XCTAssertEqual(source[i], .max) +#endif } func test_LargeSliceDataMutableSpan() throws { @@ -1715,6 +1725,7 @@ class DataTests : XCTestCase { #error("This test needs updating") #endif +#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data(repeating: 0, count: count).dropFirst() XCTAssertNotEqual(source.startIndex, 0) count = source.count @@ -1724,11 +1735,13 @@ class DataTests : XCTestCase { span[i] = .max XCTAssertEqual(source[i], 0) XCTAssertEqual(source[i+1], .max) +#endif } func test_InlineDataMutableRawSpan() throws { guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } +#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data() var span = source.mutableBytes XCTAssertTrue(span.isEmpty) @@ -1742,11 +1755,13 @@ class DataTests : XCTestCase { let v = UInt8.random(in: 10..<100) span.storeBytes(of: v, toByteOffset: i, as: UInt8.self) XCTAssertEqual(source[i], v) +#endif } func test_InlineSliceDataMutableRawSpan() throws { guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } +#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data(0..<100) let count = source.count var span = source.mutableBytes @@ -1754,6 +1769,7 @@ class DataTests : XCTestCase { let i = try XCTUnwrap(span.byteOffsets.randomElement()) span.storeBytes(of: -1, toByteOffset: i, as: Int8.self) XCTAssertEqual(source[i], .max) +#endif } func test_LargeSliceDataMutableRawSpan() throws { @@ -1767,6 +1783,7 @@ class DataTests : XCTestCase { #error("This test needs updating") #endif +#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data(repeating: 0, count: count).dropFirst() XCTAssertNotEqual(source.startIndex, 0) count = source.count @@ -1776,6 +1793,7 @@ class DataTests : XCTestCase { span.storeBytes(of: -1, toByteOffset: i, as: Int8.self) XCTAssertEqual(source[i], 0) XCTAssertEqual(source[i+1], .max) +#endif } #if false // FIXME: XCTest doesn't support crash tests yet rdar://20195010&22387653 From 9e8be796a19262efefc3023fc83e0e4ea1c1a111 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 30 May 2025 18:08:34 -0700 Subject: [PATCH 07/10] update linux build flags --- Sources/FoundationEssentials/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/FoundationEssentials/CMakeLists.txt b/Sources/FoundationEssentials/CMakeLists.txt index b88ee86d7..79435c105 100644 --- a/Sources/FoundationEssentials/CMakeLists.txt +++ b/Sources/FoundationEssentials/CMakeLists.txt @@ -74,7 +74,9 @@ endif() if(CMAKE_SYSTEM_NAME STREQUAL Linux OR CMAKE_SYSTEM_NAME STREQUAL Android) target_compile_options(FoundationEssentials PRIVATE - "SHELL:$<$:-Xfrontend -Xcc -Xfrontend -D_GNU_SOURCE>") + "SHELL:$<$:-Xfrontend -Xcc -Xfrontend -D_GNU_SOURCE>" + "SHELL:$<$:-Xfrontend -enable-experimental-feature -Xfrontend InoutLifetimeDependence>" + "SHELL:$<$:-Xfrontend -enable-experimental-feature -Xfrontend LifetimeDependenceMutableAccessors>") list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) endif() From fa9f9fb00e24c7843dd9ad1d714d739521c262f9 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Fri, 30 May 2025 19:19:14 -0700 Subject: [PATCH 08/10] shield lifetime-dependence syntax --- Package.swift | 1 + Sources/FoundationEssentials/Data/Data.swift | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 744a79207..e9a3dec09 100644 --- a/Package.swift +++ b/Package.swift @@ -145,6 +145,7 @@ let package = Package( .when(platforms: [.macOS, .iOS, .watchOS, .tvOS, .linux]) ), .enableExperimentalFeature("AddressableTypes"), + .enableExperimentalFeature("AllowUnsafeAttribute"), .enableExperimentalFeature("BuiltinModule"), .enableExperimentalFeature("AccessLevelOnImport") ] + availabilityMacros + featureSettings, diff --git a/Sources/FoundationEssentials/Data/Data.swift b/Sources/FoundationEssentials/Data/Data.swift index dca8ca372..bff800131 100644 --- a/Sources/FoundationEssentials/Data/Data.swift +++ b/Sources/FoundationEssentials/Data/Data.swift @@ -2203,6 +2203,7 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect return try _representation.withUnsafeBytes(body) } +#if compiler(>=6.2) && $LifetimeDependence @available(FoundationSpan 6.2, *) public var bytes: RawSpan { @lifetime(borrow self) @@ -2238,6 +2239,7 @@ public struct Data : Equatable, Hashable, RandomAccessCollection, MutableCollect return _overrideLifetime(span, borrowing: self) } } +#endif #if compiler(>=5.9) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors @available(FoundationSpan 6.2, *) @@ -2974,7 +2976,7 @@ extension Data : Codable { } // TODO: remove once _overrideLifetime is public in the standard library - +#if compiler(>=6.2) && $LifetimeDependence /// Unsafely discard any lifetime dependency on the `dependent` argument. Return /// a value identical to `dependent` with a lifetime dependency on the caller's /// borrow scope of the `source` argument. @@ -3006,6 +3008,7 @@ internal func _overrideLifetime< ) -> T { dependent } +#endif #if compiler(>=5.9) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors /// Unsafely discard any lifetime dependency on the `dependent` argument. From 9913bdd53c0fd1f7680990b6e3959aa1cd45a841 Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Mon, 2 Jun 2025 08:52:56 -0700 Subject: [PATCH 09/10] shield new types from testing module --- .../FoundationEssentialsTests/DataTests.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/FoundationEssentialsTests/DataTests.swift b/Tests/FoundationEssentialsTests/DataTests.swift index 0e73ad667..cb4f308b0 100644 --- a/Tests/FoundationEssentialsTests/DataTests.swift +++ b/Tests/FoundationEssentialsTests/DataTests.swift @@ -1637,7 +1637,7 @@ class DataTests : XCTestCase { func test_InlineDataSpan() throws { guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } -#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors +#if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data() var span = source.span XCTAssertTrue(span.isEmpty) @@ -1653,7 +1653,7 @@ class DataTests : XCTestCase { func test_InlineSliceDataSpan() throws { guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } -#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors +#if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors let source = Data(0 ... .max) let span = source.span XCTAssertEqual(span.count, source.count) @@ -1672,7 +1672,7 @@ class DataTests : XCTestCase { #error("This test needs updating") #endif -#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors +#if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors let source = Data(repeating: 0, count: count).dropFirst() XCTAssertNotEqual(source.startIndex, 0) let span = source.span @@ -1683,7 +1683,7 @@ class DataTests : XCTestCase { func test_InlineDataMutableSpan() throws { guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } -#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors +#if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data() var span = source.mutableSpan XCTAssertTrue(span.isEmpty) @@ -1703,7 +1703,7 @@ class DataTests : XCTestCase { func test_InlineSliceDataMutableSpan() throws { guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } -#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors +#if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data(0..<100) let count = source.count var span = source.mutableSpan @@ -1725,7 +1725,7 @@ class DataTests : XCTestCase { #error("This test needs updating") #endif -#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors +#if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data(repeating: 0, count: count).dropFirst() XCTAssertNotEqual(source.startIndex, 0) count = source.count @@ -1741,7 +1741,7 @@ class DataTests : XCTestCase { func test_InlineDataMutableRawSpan() throws { guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } -#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors +#if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data() var span = source.mutableBytes XCTAssertTrue(span.isEmpty) @@ -1761,7 +1761,7 @@ class DataTests : XCTestCase { func test_InlineSliceDataMutableRawSpan() throws { guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } -#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors +#if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data(0..<100) let count = source.count var span = source.mutableBytes @@ -1783,7 +1783,7 @@ class DataTests : XCTestCase { #error("This test needs updating") #endif -#if $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors +#if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data(repeating: 0, count: count).dropFirst() XCTAssertNotEqual(source.startIndex, 0) count = source.count From 9ed87ab8f6c4f1aa91b708c7beb4c6f05f1ced0d Mon Sep 17 00:00:00 2001 From: Guillaume Lessard Date: Mon, 2 Jun 2025 15:32:50 -0700 Subject: [PATCH 10/10] compile out MutableSpan mutations when unsupported by deployment target --- Tests/FoundationEssentialsTests/DataTests.swift | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Tests/FoundationEssentialsTests/DataTests.swift b/Tests/FoundationEssentialsTests/DataTests.swift index cb4f308b0..b076b0677 100644 --- a/Tests/FoundationEssentialsTests/DataTests.swift +++ b/Tests/FoundationEssentialsTests/DataTests.swift @@ -1683,6 +1683,7 @@ class DataTests : XCTestCase { func test_InlineDataMutableSpan() throws { guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } +#if !canImport(Darwin) || FOUNDATION_FRAMEWORK #if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data() var span = source.mutableSpan @@ -1696,21 +1697,27 @@ class DataTests : XCTestCase { XCTAssertEqual(span.count, count) let v = UInt8.random(in: 10..<100) span[i] = v + var sub = span.extracting(i ..< i+1) + sub.update(repeating: v) XCTAssertEqual(source[i], v) +#endif #endif } func test_InlineSliceDataMutableSpan() throws { guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } +#if !canImport(Darwin) || FOUNDATION_FRAMEWORK #if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data(0..<100) let count = source.count var span = source.mutableSpan XCTAssertEqual(span.count, count) let i = try XCTUnwrap(span.indices.randomElement()) - span[i] = .max + var sub = span.extracting(i..=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors var source = Data(repeating: 0, count: count).dropFirst() XCTAssertNotEqual(source.startIndex, 0) @@ -1735,6 +1743,7 @@ class DataTests : XCTestCase { span[i] = .max XCTAssertEqual(source[i], 0) XCTAssertEqual(source[i+1], .max) +#endif #endif } @@ -1753,7 +1762,8 @@ class DataTests : XCTestCase { XCTAssertFalse(span.isEmpty) XCTAssertEqual(span.byteCount, count) let v = UInt8.random(in: 10..<100) - span.storeBytes(of: v, toByteOffset: i, as: UInt8.self) + var sub = span.extracting(i..