diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c21d7e3c..12eb7612d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,13 +111,15 @@ 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 ${_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/Package.swift b/Package.swift index de020cf03..e9a3dec09 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: .future), // Availability of Span types ] let versionNumbers = ["0.1", "0.2", "0.3", "0.4", "6.0.2", "6.1", "6.2"] @@ -134,6 +135,18 @@ let package = Package( ] + wasiLibcCSettings, 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("AllowUnsafeAttribute"), + .enableExperimentalFeature("BuiltinModule"), .enableExperimentalFeature("AccessLevelOnImport") ] + availabilityMacros + featureSettings, linkerSettings: [ @@ -149,7 +162,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/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/CMakeLists.txt b/Sources/FoundationEssentials/CMakeLists.txt index afdbdd507..79435c105 100644 --- a/Sources/FoundationEssentials/CMakeLists.txt +++ b/Sources/FoundationEssentials/CMakeLists.txt @@ -74,12 +74,17 @@ 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() 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>" diff --git a/Sources/FoundationEssentials/Data/Data.swift b/Sources/FoundationEssentials/Data/Data.swift index 9f5bb7245..bff800131 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,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 @@ -2198,7 +2202,107 @@ 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) && $LifetimeDependence + @available(FoundationSpan 6.2, *) + public var bytes: RawSpan { + @lifetime(borrow self) + borrowing get { + let buffer: UnsafeRawBufferPointer + switch _representation { + case .empty: + buffer = UnsafeRawBufferPointer(start: nil, count: 0) + 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) + } + } +#endif + +#if compiler(>=5.9) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors + @available(FoundationSpan 6.2, *) + public var mutableBytes: MutableRawSpan { + @lifetime(&self) + mutating get { + let buffer: UnsafeMutableRawBufferPointer + switch _representation { + case .empty: + buffer = UnsafeMutableRawBufferPointer(start: nil, count: 0) + 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 // see https://github.com/swiftlang/swift/issues/81218 + 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(start: nil, count: 0) + 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 // $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors + @_alwaysEmitIntoClient public func withContiguousStorageIfAvailable(_ body: (_ buffer: UnsafeBufferPointer) throws -> ResultType) rethrows -> ResultType? { return try _representation.withUnsafeBytes { @@ -2870,3 +2974,57 @@ 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. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@lifetime(borrow source) +internal func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, borrowing source: borrowing U +) -> T { + 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 { + dependent +} +#endif + +#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. +@unsafe +@_unsafeNonescapableResult +@_alwaysEmitIntoClient +@_transparent +@lifetime(&source) +internal func _overrideLifetime< + T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable +>( + _ dependent: consuming T, + mutating source: inout U +) -> T { + dependent +} +#endif // $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors diff --git a/Tests/FoundationEssentialsTests/DataTests.swift b/Tests/FoundationEssentialsTests/DataTests.swift index ef820dbab..b076b0677 100644 --- a/Tests/FoundationEssentialsTests/DataTests.swift +++ b/Tests/FoundationEssentialsTests/DataTests.swift @@ -1634,6 +1634,177 @@ class DataTests : XCTestCase { // source.advanced(by: 5) } + func test_InlineDataSpan() throws { + guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } + +#if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors + 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) +#endif + } + + func test_InlineSliceDataSpan() throws { + guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } + +#if compiler(>=6.2) && $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 { + guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } + +#if _pointerBitWidth(_64) + let count = Int(Int32.max) +#elseif _pointerBitWidth(_32) + let count = Int(Int16.max) +#else + #error("This test needs updating") +#endif + +#if compiler(>=6.2) && $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 !canImport(Darwin) || FOUNDATION_FRAMEWORK +#if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors + 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 + 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()) + var sub = span.extracting(i..=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors + 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) +#endif +#endif + } + + func test_InlineDataMutableRawSpan() throws { + guard #available(FoundationSpan 6.2, *) else { throw XCTSkip("Span not available") } + +#if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors + 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) + var sub = span.extracting(i..=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors + 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) +#endif + } + + 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) + var count = Int(Int16.max) +#else + #error("This test needs updating") +#endif + +#if compiler(>=6.2) && $InoutLifetimeDependence && $LifetimeDependenceMutableAccessors + 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) +#endif + } #if false // FIXME: XCTest doesn't support crash tests yet rdar://20195010&22387653 func test_bounding_failure_subdata() {