From 9760cccc608819ab983d8ce03490e7364a5a51d7 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 29 Oct 2025 15:19:37 -0700 Subject: [PATCH 01/11] Generalize ContiguousBytes to be noncopyable and nonescapable for spans The ContiguousBytes protocol covers various types that can provide unsafe access to their stored bytes. Generalize the ContiguousBytes protocol by making it non-copyable and non-escapable, so that the Span family of types and InlineArray can conform to it. Fixes rdar://163716671. --- .../Data/ContiguousBytes.swift | 46 +++++++++++++++++++ .../ContiguousBytesTests.swift | 41 +++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 Tests/FoundationEssentialsTests/ContiguousBytesTests.swift diff --git a/Sources/FoundationEssentials/Data/ContiguousBytes.swift b/Sources/FoundationEssentials/Data/ContiguousBytes.swift index c7569c6d7..1eec9ef4d 100644 --- a/Sources/FoundationEssentials/Data/ContiguousBytes.swift +++ b/Sources/FoundationEssentials/Data/ContiguousBytes.swift @@ -12,6 +12,20 @@ //===--- ContiguousBytes --------------------------------------------------===// +#if compiler(>=6.2) +/// Indicates that the conforming type is a contiguous collection of raw bytes +/// whose underlying storage is directly accessible by withUnsafeBytes. +@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) +public protocol ContiguousBytes: ~Escapable, ~Copyable { + /// Calls the given closure with the contents of underlying storage. + /// + /// - note: Calling `withUnsafeBytes` multiple times does not guarantee that + /// the same buffer pointer will be passed in every time. + /// - warning: The buffer argument to the body should not be stored or used + /// outside of the lifetime of the call to the closure. + func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R +} +#else /// Indicates that the conforming type is a contiguous collection of raw bytes /// whose underlying storage is directly accessible by withUnsafeBytes. @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) @@ -24,6 +38,7 @@ public protocol ContiguousBytes { /// outside of the lifetime of the call to the closure. func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R } +#endif //===--- Collection Conformances ------------------------------------------===// @@ -109,3 +124,34 @@ extension Slice : ContiguousBytes where Base : ContiguousBytes { } } } + +#if compiler(>=6.2) + +//===--- Span Conformances -----------------------------------------===// + +@available(FoundationPreview 6.3, *) +extension RawSpan: ContiguousBytes { +} + +@available(FoundationPreview 6.3, *) +extension MutableRawSpan: ContiguousBytes { +} + +@available(FoundationPreview 6.3, *) +extension Span: ContiguousBytes where Element == UInt8 { +} + +@available(FoundationPreview 6.3, *) +extension MutableSpan: ContiguousBytes where Element == UInt8 { +} + +@available(FoundationPreview 6.3, *) +@available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *) +extension InlineArray: ContiguousBytes where Element == UInt8 { + @_alwaysEmitIntoClient + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { + return try span.withUnsafeBytes(body) + } +} + +#endif diff --git a/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift b/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift new file mode 100644 index 000000000..920b1de06 --- /dev/null +++ b/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift @@ -0,0 +1,41 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Testing + +#if canImport(FoundationEssentials) +@testable import FoundationEssentials +#else +@testable import Foundation +#endif + +#if compiler(>=6.2) + +func acceptContiguousBytes(_ bytes: borrowing some ContiguousBytes & ~Escapable & ~Copyable) { } + +@Suite("ContiguousBytesTests") +private struct ContiguousBytesTests { + @available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *) + @Test func span() throws { + if #available(FoundationPreview 6.3, *) { + var bytes: [UInt8] = [1, 2, 3] + acceptContiguousBytes(bytes.span) + acceptContiguousBytes(bytes.mutableSpan) + acceptContiguousBytes(bytes.span.bytes) + + let ms = bytes.mutableSpan + acceptContiguousBytes(ms.bytes) + } + } +} + +#endif From fd55250a3db4c0b81e657ed688bdb15e2a858bc1 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 6 Nov 2025 14:27:47 -0800 Subject: [PATCH 02/11] Address availability and compiler version issues with non-escaping ContiguousBytes Introduce a new availability macro, FoundationInlineArray, which relies on features that are part of the Swift 6.2 standard library, and use that for InlineArray's conformance to ContiguousBytes. Also remove the #if compiler checks I added to this code. We can assume a Swift 6.2 (or newer) compiler. --- CMakeLists.txt | 7 ++++-- Package.swift | 1 + .../Data/ContiguousBytes.swift | 22 +------------------ .../ContiguousBytesTests.swift | 20 ++++++++--------- 4 files changed, 16 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 31638c287..b9bf520ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,6 +94,7 @@ list(APPEND CMAKE_MODULE_PATH ${SwiftFoundation_SOURCE_DIR}/cmake/modules) # Availability Macros (only applies to FoundationEssentials and FoundationInternationalization) set(_SwiftFoundation_BaseAvailability "macOS 15, iOS 18, tvOS 18, watchOS 11") +set(_SwiftFoundation_InlineArrayAvailability "macOS 26, iOS 26, tvOS 26, watchOS 26, visionOS 26") set(_SwiftFoundation_FutureAvailability "macOS 10000, iOS 10000, tvOS 10000, watchOS 10000") # All versions to define for each availability name @@ -106,11 +107,13 @@ list(APPEND _SwiftFoundation_versions # Each availability name to define list(APPEND _SwiftFoundation_availability_names - "FoundationPreview") + "FoundationPreview" + "FoundationInlineArray") # The aligned availability for each name (in the same order) list(APPEND _SwiftFoundation_availability_releases - ${_SwiftFoundation_BaseAvailability}) + ${_SwiftFoundation_BaseAvailability} + ${_SwiftFoundation_InlineArrayAvailability}) 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 a2b1ccf4b..fffeba972 100644 --- a/Package.swift +++ b/Package.swift @@ -8,6 +8,7 @@ import CompilerPluginSupport let availabilityTags: [_Availability] = [ _Availability("FoundationPreview"), // Default FoundationPreview availability + _Availability("FoundationInlineArray", availability: .macOS26) // Availability of InlineArray ] let versionNumbers = ["6.0.2", "6.1", "6.2", "6.3"] diff --git a/Sources/FoundationEssentials/Data/ContiguousBytes.swift b/Sources/FoundationEssentials/Data/ContiguousBytes.swift index 1eec9ef4d..814ae6793 100644 --- a/Sources/FoundationEssentials/Data/ContiguousBytes.swift +++ b/Sources/FoundationEssentials/Data/ContiguousBytes.swift @@ -12,7 +12,6 @@ //===--- ContiguousBytes --------------------------------------------------===// -#if compiler(>=6.2) /// Indicates that the conforming type is a contiguous collection of raw bytes /// whose underlying storage is directly accessible by withUnsafeBytes. @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) @@ -25,20 +24,6 @@ public protocol ContiguousBytes: ~Escapable, ~Copyable { /// outside of the lifetime of the call to the closure. func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R } -#else -/// Indicates that the conforming type is a contiguous collection of raw bytes -/// whose underlying storage is directly accessible by withUnsafeBytes. -@available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) -public protocol ContiguousBytes { - /// Calls the given closure with the contents of underlying storage. - /// - /// - note: Calling `withUnsafeBytes` multiple times does not guarantee that - /// the same buffer pointer will be passed in every time. - /// - warning: The buffer argument to the body should not be stored or used - /// outside of the lifetime of the call to the closure. - func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R -} -#endif //===--- Collection Conformances ------------------------------------------===// @@ -125,8 +110,6 @@ extension Slice : ContiguousBytes where Base : ContiguousBytes { } } -#if compiler(>=6.2) - //===--- Span Conformances -----------------------------------------===// @available(FoundationPreview 6.3, *) @@ -145,13 +128,10 @@ extension Span: ContiguousBytes where Element == UInt8 { extension MutableSpan: ContiguousBytes where Element == UInt8 { } -@available(FoundationPreview 6.3, *) -@available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *) +@available(FoundationInlineArray 6.3, *) extension InlineArray: ContiguousBytes where Element == UInt8 { @_alwaysEmitIntoClient public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { return try span.withUnsafeBytes(body) } } - -#endif diff --git a/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift b/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift index 920b1de06..7b9c0000f 100644 --- a/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift +++ b/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift @@ -18,24 +18,22 @@ import Testing @testable import Foundation #endif -#if compiler(>=6.2) - -func acceptContiguousBytes(_ bytes: borrowing some ContiguousBytes & ~Escapable & ~Copyable) { } +func acceptContiguousBytes(_ bytes: borrowing T) { } @Suite("ContiguousBytesTests") private struct ContiguousBytesTests { - @available(macOS 26.0, iOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *) @Test func span() throws { if #available(FoundationPreview 6.3, *) { var bytes: [UInt8] = [1, 2, 3] - acceptContiguousBytes(bytes.span) - acceptContiguousBytes(bytes.mutableSpan) - acceptContiguousBytes(bytes.span.bytes) + bytes.withUnsafeMutableBufferPointer { unsafeBytes in + acceptContiguousBytes(unsafeBytes.span) + acceptContiguousBytes(unsafeBytes.mutableSpan) + acceptContiguousBytes(unsafeBytes.span.bytes) - let ms = bytes.mutableSpan - acceptContiguousBytes(ms.bytes) + var ms = unsafeBytes.mutableSpan + acceptContiguousBytes(ms.bytes) + acceptContiguousBytes(ms.mutableBytes) + } } } } - -#endif From 6f4d2b9efbeff308414760b6d2e5638f2ebab7a3 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 6 Nov 2025 16:16:30 -0800 Subject: [PATCH 03/11] Adopt typed throws in ContiguousBytes and its conformers For each of the conformances to ContiguousBytes, adopt typed throws in the withUnsafeBytes functions. When not building for Embedded Swift, leave in the old implementation as a @usableFromInline internal function to continue to expose the untyped (rethrowing) version in the binary. In the ContiguousBytes protocol itself, we don't have a good way to introduce the typed-throws variant without breaking the ABI, so settle for adopting typed throws only in Embedded Swift until we can sort that out. --- .../Data/ContiguousBytes.swift | 112 ++++++++++++++++-- 1 file changed, 99 insertions(+), 13 deletions(-) diff --git a/Sources/FoundationEssentials/Data/ContiguousBytes.swift b/Sources/FoundationEssentials/Data/ContiguousBytes.swift index 814ae6793..568107533 100644 --- a/Sources/FoundationEssentials/Data/ContiguousBytes.swift +++ b/Sources/FoundationEssentials/Data/ContiguousBytes.swift @@ -16,6 +16,7 @@ /// whose underlying storage is directly accessible by withUnsafeBytes. @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) public protocol ContiguousBytes: ~Escapable, ~Copyable { +#if !hasFeature(Embedded) /// Calls the given closure with the contents of underlying storage. /// /// - note: Calling `withUnsafeBytes` multiple times does not guarantee that @@ -23,6 +24,15 @@ public protocol ContiguousBytes: ~Escapable, ~Copyable { /// - warning: The buffer argument to the body should not be stored or used /// outside of the lifetime of the call to the closure. func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R +#else + /// Calls the given closure with the contents of underlying storage. + /// + /// - note: Calling `withUnsafeBytes` multiple times does not guarantee that + /// the same buffer pointer will be passed in every time. + /// - warning: The buffer argument to the body should not be stored or used + /// outside of the lifetime of the call to the closure. + func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R +#endif } //===--- Collection Conformances ------------------------------------------===// @@ -43,16 +53,34 @@ extension ContiguousArray : ContiguousBytes where Element == UInt8 { } @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) extension UnsafeRawBufferPointer : ContiguousBytes { - @inlinable - public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + #if !hasFeature(Embedded) + // Historical ABI + @usableFromInline + @abi(func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R) + func __abi__withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + return try body(self) + } + #endif + + @_alwaysEmitIntoClient + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { return try body(self) } } @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) extension UnsafeMutableRawBufferPointer : ContiguousBytes { - @inlinable - public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { +#if !hasFeature(Embedded) + // Historical ABI + @usableFromInline + @abi(func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R) + func __abi__withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + return try body(UnsafeRawBufferPointer(self)) + } +#endif + + @_alwaysEmitIntoClient + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { return try body(UnsafeRawBufferPointer(self)) } } @@ -60,8 +88,16 @@ extension UnsafeMutableRawBufferPointer : ContiguousBytes { // FIXME: When possible, expand conformance to `where Element : Trivial`. @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) extension UnsafeBufferPointer : ContiguousBytes where Element == UInt8 { - @inlinable - public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { +#if !hasFeature(Embedded) + @usableFromInline + @abi(func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R) + func __abi__withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + return try body(UnsafeRawBufferPointer(self)) + } +#endif + + @_alwaysEmitIntoClient + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { return try body(UnsafeRawBufferPointer(self)) } } @@ -69,8 +105,16 @@ extension UnsafeBufferPointer : ContiguousBytes where Element == UInt8 { // FIXME: When possible, expand conformance to `where Element : Trivial`. @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) extension UnsafeMutableBufferPointer : ContiguousBytes where Element == UInt8 { - @inlinable - public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { +#if !hasFeature(Embedded) + @usableFromInline + @abi(func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R) + func __abi__withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + return try body(UnsafeRawBufferPointer(self)) + } +#endif + + @_alwaysEmitIntoClient + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { return try body(UnsafeRawBufferPointer(self)) } } @@ -78,8 +122,16 @@ extension UnsafeMutableBufferPointer : ContiguousBytes where Element == UInt8 { // FIXME: When possible, expand conformance to `where Element : Trivial`. @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) extension EmptyCollection : ContiguousBytes where Element == UInt8 { - @inlinable - public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { +#if !hasFeature(Embedded) + @usableFromInline + @abi(func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R) + func __abi__withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + return try body(UnsafeRawBufferPointer(start: nil, count: 0)) + } +#endif + + @_alwaysEmitIntoClient + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { return try body(UnsafeRawBufferPointer(start: nil, count: 0)) } } @@ -87,20 +139,34 @@ extension EmptyCollection : ContiguousBytes where Element == UInt8 { // FIXME: When possible, expand conformance to `where Element : Trivial`. @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) extension CollectionOfOne : ContiguousBytes where Element == UInt8 { - @inlinable - public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { +#if !hasFeature(Embedded) + @usableFromInline + @abi(func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R) + func __abi__withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { let element = self.first! return try Swift.withUnsafeBytes(of: element) { return try body($0) } } +#endif + + @_alwaysEmitIntoClient + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { + let element = self.first! + return try Swift.withUnsafeBytes(of: element) { (buffer) throws(E) in + return try body(buffer) + } + } } //===--- Conditional Conformances -----------------------------------------===// @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) extension Slice : ContiguousBytes where Base : ContiguousBytes { - public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> ResultType) rethrows -> ResultType { +#if !hasFeature(Embedded) + @usableFromInline + @abi(func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R) + func __abi__withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> ResultType) rethrows -> ResultType { let offset = base.distance(from: base.startIndex, to: self.startIndex) return try base.withUnsafeBytes { ptr in let slicePtr = ptr.baseAddress?.advanced(by: offset) @@ -108,6 +174,26 @@ extension Slice : ContiguousBytes where Base : ContiguousBytes { return try body(sliceBuffer) } } +#endif + + @_alwaysEmitIntoClient + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(ErrorType) -> ResultType) throws(ErrorType) -> ResultType { + let offset = base.distance(from: base.startIndex, to: self.startIndex) + do { + return try base.withUnsafeBytes { (ptr) throws(ErrorType) in + let slicePtr = ptr.baseAddress?.advanced(by: offset) + let sliceBuffer = UnsafeRawBufferPointer(start: slicePtr, count: self.count) + return try body(sliceBuffer) + } + } catch let error { +#if !hasFeature(Embedded) + // Note: withUnsafeBytes is rethrowing, so we have an "any Error" here that needs casting. + throw error as! ErrorType +#else + throw error +#endif + } + } } //===--- Span Conformances -----------------------------------------===// From d71aef0ec37eccebf9cabe5b464eb173003d3f54 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 6 Nov 2025 16:45:05 -0800 Subject: [PATCH 04/11] Extend ContiguousBytes with a `withBytes` operation The `withBytes` operation is the safe counterpart to `withUnsafeBytes`. It provides a RawSpan to its closure, rather than an unsafe raw buffer pointer, to ensure that the pointer does not escape the closure. There is a default implementation of `withBytes` built on top of `withUnsafeBytes` in the obvious way. It also papers over the Embedded-vs-Desktop differences by always providing a typed-throws API. --- .../Data/ContiguousBytes.swift | 29 +++++++++++++++++++ .../ContiguousBytesTests.swift | 22 +++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/Sources/FoundationEssentials/Data/ContiguousBytes.swift b/Sources/FoundationEssentials/Data/ContiguousBytes.swift index 568107533..5c0d5b684 100644 --- a/Sources/FoundationEssentials/Data/ContiguousBytes.swift +++ b/Sources/FoundationEssentials/Data/ContiguousBytes.swift @@ -33,6 +33,35 @@ public protocol ContiguousBytes: ~Escapable, ~Copyable { /// outside of the lifetime of the call to the closure. func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R #endif + + /// Calls the given closure with the contents of underlying storage. + /// + /// - note: Calling `withUnsafeBytes` multiple times does not guarantee that + /// the same span will be passed in every time. + @available(FoundationPreview 6.3, *) + func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R +} + +extension ContiguousBytes where Self: ~Escapable, Self: ~Copyable { + /// Calls the given closure with the contents of underlying storage. + /// + /// - note: Calling `withUnsafeBytes` multiple times does not guarantee that + /// the same span will be passed in every time. + @_alwaysEmitIntoClient + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + do { + return try withUnsafeBytes { + try body($0.bytes) + } + } catch let error { +#if !hasFeature(Embedded) + // Note: withUnsafeBytes is rethrowing, so we have an "any Error" here that needs casting. + throw error as! E +#else + throw error +#endif + } + } } //===--- Collection Conformances ------------------------------------------===// diff --git a/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift b/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift index 7b9c0000f..20bc7df77 100644 --- a/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift +++ b/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift @@ -18,7 +18,27 @@ import Testing @testable import Foundation #endif -func acceptContiguousBytes(_ bytes: borrowing T) { } +enum HomeworkError: Error { + case dogAteIt +} + +@available(FoundationPreview 6.3, *) +@discardableResult +func acceptContiguousBytes(_ bytes: borrowing T) -> Int { + do { + // Ensure that we can use withBytes with typed throws. + return try bytes.withBytes { (buffer) throws(HomeworkError) in + if buffer.isEmpty { + throw .dogAteIt + } + + return buffer.byteCount + } + } catch let error { + precondition(error == .dogAteIt) + return -1 + } +} @Suite("ContiguousBytesTests") private struct ContiguousBytesTests { From 892f17beca370d40e3bfac9848fb624f5932e41f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 6 Nov 2025 17:13:12 -0800 Subject: [PATCH 05/11] Work around compiler issue with typed throws --- .../Data/ContiguousBytes.swift | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Sources/FoundationEssentials/Data/ContiguousBytes.swift b/Sources/FoundationEssentials/Data/ContiguousBytes.swift index 5c0d5b684..3bf043fcf 100644 --- a/Sources/FoundationEssentials/Data/ContiguousBytes.swift +++ b/Sources/FoundationEssentials/Data/ContiguousBytes.swift @@ -49,18 +49,20 @@ extension ContiguousBytes where Self: ~Escapable, Self: ~Copyable { /// the same span will be passed in every time. @_alwaysEmitIntoClient public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { +#if !hasFeature(Embedded) do { - return try withUnsafeBytes { - try body($0.bytes) + return try withUnsafeBytes { (buffer) in + try body(buffer.bytes) } } catch let error { -#if !hasFeature(Embedded) // Note: withUnsafeBytes is rethrowing, so we have an "any Error" here that needs casting. throw error as! E + } #else - throw error -#endif + return try withUnsafeBytes { (buffer) throws(E) in + try body(buffer.bytes) } +#endif } } @@ -208,20 +210,25 @@ extension Slice : ContiguousBytes where Base : ContiguousBytes { @_alwaysEmitIntoClient public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(ErrorType) -> ResultType) throws(ErrorType) -> ResultType { let offset = base.distance(from: base.startIndex, to: self.startIndex) + +#if !hasFeature(Embedded) do { - return try base.withUnsafeBytes { (ptr) throws(ErrorType) in + return try base.withUnsafeBytes { (ptr) in let slicePtr = ptr.baseAddress?.advanced(by: offset) let sliceBuffer = UnsafeRawBufferPointer(start: slicePtr, count: self.count) return try body(sliceBuffer) } } catch let error { -#if !hasFeature(Embedded) // Note: withUnsafeBytes is rethrowing, so we have an "any Error" here that needs casting. throw error as! ErrorType + } #else - throw error -#endif + return try base.withUnsafeBytes { (ptr) throws(ErrorType) in + let slicePtr = ptr.baseAddress?.advanced(by: offset) + let sliceBuffer = UnsafeRawBufferPointer(start: slicePtr, count: self.count) + return try body(sliceBuffer) } +#endif } } From ec458d8ac986c2fd8a789979f20f8734ca168b4d Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 7 Nov 2025 11:46:27 -0800 Subject: [PATCH 06/11] ContiguousBytes: Make UTF8Span conform and provide a few minor cleanups --- .../Data/ContiguousBytes.swift | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/Sources/FoundationEssentials/Data/ContiguousBytes.swift b/Sources/FoundationEssentials/Data/ContiguousBytes.swift index 3bf043fcf..df93f425b 100644 --- a/Sources/FoundationEssentials/Data/ContiguousBytes.swift +++ b/Sources/FoundationEssentials/Data/ContiguousBytes.swift @@ -13,7 +13,7 @@ //===--- ContiguousBytes --------------------------------------------------===// /// Indicates that the conforming type is a contiguous collection of raw bytes -/// whose underlying storage is directly accessible by withUnsafeBytes. +/// whose underlying storage is directly accessible by withBytes. @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) public protocol ContiguousBytes: ~Escapable, ~Copyable { #if !hasFeature(Embedded) @@ -36,7 +36,7 @@ public protocol ContiguousBytes: ~Escapable, ~Copyable { /// Calls the given closure with the contents of underlying storage. /// - /// - note: Calling `withUnsafeBytes` multiple times does not guarantee that + /// - note: Calling `withBytes` multiple times does not guarantee that /// the same span will be passed in every time. @available(FoundationPreview 6.3, *) func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R @@ -45,7 +45,7 @@ public protocol ContiguousBytes: ~Escapable, ~Copyable { extension ContiguousBytes where Self: ~Escapable, Self: ~Copyable { /// Calls the given closure with the contents of underlying storage. /// - /// - note: Calling `withUnsafeBytes` multiple times does not guarantee that + /// - note: Calling `withBytes` multiple times does not guarantee that /// the same span will be passed in every time. @_alwaysEmitIntoClient public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { @@ -236,18 +236,42 @@ extension Slice : ContiguousBytes where Base : ContiguousBytes { @available(FoundationPreview 6.3, *) extension RawSpan: ContiguousBytes { + @_alwaysEmitIntoClient + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + try body(self) + } } @available(FoundationPreview 6.3, *) extension MutableRawSpan: ContiguousBytes { + @_alwaysEmitIntoClient + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + try body(bytes) + } +} + +@available(FoundationInlineArray 6.3, *) +extension UTF8Span: ContiguousBytes { + @_alwaysEmitIntoClient + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + try span.withUnsafeBytes(body) + } } @available(FoundationPreview 6.3, *) extension Span: ContiguousBytes where Element == UInt8 { + @_alwaysEmitIntoClient + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + try body(bytes) + } } @available(FoundationPreview 6.3, *) extension MutableSpan: ContiguousBytes where Element == UInt8 { + @_alwaysEmitIntoClient + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + try body(bytes) + } } @available(FoundationInlineArray 6.3, *) @@ -256,4 +280,9 @@ extension InlineArray: ContiguousBytes where Element == UInt8 { public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { return try span.withUnsafeBytes(body) } + + @_alwaysEmitIntoClient + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + try body(span.bytes) + } } From d6e88d2ffb03c6193ef972f16ee0f8754135ecb0 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 7 Nov 2025 11:54:38 -0800 Subject: [PATCH 07/11] Generalize concrete withBytes operations to allow non-copyable result type --- .../Data/ContiguousBytes.swift | 41 ++++++++++++++++--- .../ContiguousBytesTests.swift | 7 ++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/Sources/FoundationEssentials/Data/ContiguousBytes.swift b/Sources/FoundationEssentials/Data/ContiguousBytes.swift index df93f425b..0f8bd9592 100644 --- a/Sources/FoundationEssentials/Data/ContiguousBytes.swift +++ b/Sources/FoundationEssentials/Data/ContiguousBytes.swift @@ -97,6 +97,12 @@ extension UnsafeRawBufferPointer : ContiguousBytes { public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { return try body(self) } + + + @_alwaysEmitIntoClient + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + return try body(bytes) + } } @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) @@ -114,6 +120,11 @@ extension UnsafeMutableRawBufferPointer : ContiguousBytes { public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { return try body(UnsafeRawBufferPointer(self)) } + + @_alwaysEmitIntoClient + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + return try body(bytes) + } } // FIXME: When possible, expand conformance to `where Element : Trivial`. @@ -131,6 +142,11 @@ extension UnsafeBufferPointer : ContiguousBytes where Element == UInt8 { public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { return try body(UnsafeRawBufferPointer(self)) } + + @_alwaysEmitIntoClient + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + return try body(span.bytes) + } } // FIXME: When possible, expand conformance to `where Element : Trivial`. @@ -148,6 +164,11 @@ extension UnsafeMutableBufferPointer : ContiguousBytes where Element == UInt8 { public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { return try body(UnsafeRawBufferPointer(self)) } + + @_alwaysEmitIntoClient + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + return try body(span.bytes) + } } // FIXME: When possible, expand conformance to `where Element : Trivial`. @@ -240,13 +261,18 @@ extension RawSpan: ContiguousBytes { public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { try body(self) } + + @_alwaysEmitIntoClient + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + return try body(self) + } } @available(FoundationPreview 6.3, *) extension MutableRawSpan: ContiguousBytes { @_alwaysEmitIntoClient - public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { - try body(bytes) + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + return try body(bytes) } } @@ -256,12 +282,17 @@ extension UTF8Span: ContiguousBytes { public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { try span.withUnsafeBytes(body) } + + @_alwaysEmitIntoClient + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + return try body(span.bytes) + } } @available(FoundationPreview 6.3, *) extension Span: ContiguousBytes where Element == UInt8 { @_alwaysEmitIntoClient - public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { try body(bytes) } } @@ -269,7 +300,7 @@ extension Span: ContiguousBytes where Element == UInt8 { @available(FoundationPreview 6.3, *) extension MutableSpan: ContiguousBytes where Element == UInt8 { @_alwaysEmitIntoClient - public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { try body(bytes) } } @@ -282,7 +313,7 @@ extension InlineArray: ContiguousBytes where Element == UInt8 { } @_alwaysEmitIntoClient - public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { try body(span.bytes) } } diff --git a/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift b/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift index 20bc7df77..5e0ed5b1f 100644 --- a/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift +++ b/Tests/FoundationEssentialsTests/ContiguousBytesTests.swift @@ -40,6 +40,8 @@ func acceptContiguousBytes(_ bytes: } } +struct NC: ~Copyable { } + @Suite("ContiguousBytesTests") private struct ContiguousBytesTests { @Test func span() throws { @@ -53,6 +55,11 @@ private struct ContiguousBytesTests { var ms = unsafeBytes.mutableSpan acceptContiguousBytes(ms.bytes) acceptContiguousBytes(ms.mutableBytes) + + // Noncopyable result type + _ = unsafeBytes.span.withBytes { (buffer) in + return NC() + } } } } From f8b3fbeb9c43b66143757b3117722356aa18734c Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 7 Nov 2025 12:49:43 -0800 Subject: [PATCH 08/11] Minor cleanups --- Sources/FoundationEssentials/Data/ContiguousBytes.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Sources/FoundationEssentials/Data/ContiguousBytes.swift b/Sources/FoundationEssentials/Data/ContiguousBytes.swift index 0f8bd9592..39b7c7449 100644 --- a/Sources/FoundationEssentials/Data/ContiguousBytes.swift +++ b/Sources/FoundationEssentials/Data/ContiguousBytes.swift @@ -98,7 +98,6 @@ extension UnsafeRawBufferPointer : ContiguousBytes { return try body(self) } - @_alwaysEmitIntoClient public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { return try body(bytes) @@ -257,11 +256,6 @@ extension Slice : ContiguousBytes where Base : ContiguousBytes { @available(FoundationPreview 6.3, *) extension RawSpan: ContiguousBytes { - @_alwaysEmitIntoClient - public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { - try body(self) - } - @_alwaysEmitIntoClient public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { return try body(self) From d5e426fc02cf077b5c10580135eb012c281a7472 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 7 Nov 2025 13:01:51 -0800 Subject: [PATCH 09/11] Fix a missed typed throws that affects Embedded Swift --- Sources/FoundationEssentials/Data/ContiguousBytes.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/FoundationEssentials/Data/ContiguousBytes.swift b/Sources/FoundationEssentials/Data/ContiguousBytes.swift index 39b7c7449..4bc6e90df 100644 --- a/Sources/FoundationEssentials/Data/ContiguousBytes.swift +++ b/Sources/FoundationEssentials/Data/ContiguousBytes.swift @@ -273,7 +273,7 @@ extension MutableRawSpan: ContiguousBytes { @available(FoundationInlineArray 6.3, *) extension UTF8Span: ContiguousBytes { @_alwaysEmitIntoClient - public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R { + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { try span.withUnsafeBytes(body) } From 708c11c8c94590c6ee8ee5d3ab572a60ffaaa914 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 7 Nov 2025 22:13:58 -0800 Subject: [PATCH 10/11] Make OutputRawSpan and OutputSpan conform to ContiguousBytes --- .../Data/ContiguousBytes.swift | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Sources/FoundationEssentials/Data/ContiguousBytes.swift b/Sources/FoundationEssentials/Data/ContiguousBytes.swift index 4bc6e90df..83f57d77d 100644 --- a/Sources/FoundationEssentials/Data/ContiguousBytes.swift +++ b/Sources/FoundationEssentials/Data/ContiguousBytes.swift @@ -270,6 +270,19 @@ extension MutableRawSpan: ContiguousBytes { } } +@available(FoundationPreview 6.3, *) +extension OutputRawSpan: ContiguousBytes { + @_alwaysEmitIntoClient + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { + try bytes.withUnsafeBytes(body) + } + + @_alwaysEmitIntoClient + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + try body(bytes) + } +} + @available(FoundationInlineArray 6.3, *) extension UTF8Span: ContiguousBytes { @_alwaysEmitIntoClient @@ -299,6 +312,19 @@ extension MutableSpan: ContiguousBytes where Element == UInt8 { } } +@available(FoundationPreview 6.3, *) +extension OutputSpan: ContiguousBytes where Element == UInt8 { + @_alwaysEmitIntoClient + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { + try span.withUnsafeBytes(body) + } + + @_alwaysEmitIntoClient + public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { + try body(span.bytes) + } +} + @available(FoundationInlineArray 6.3, *) extension InlineArray: ContiguousBytes where Element == UInt8 { @_alwaysEmitIntoClient From 5fc7a2e548d3b47575f205f052a94aa6fb24b8f9 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 7 Nov 2025 22:20:34 -0800 Subject: [PATCH 11/11] Make the concrete withBytes methods available independently of the conformance --- .../Data/ContiguousBytes.swift | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/Sources/FoundationEssentials/Data/ContiguousBytes.swift b/Sources/FoundationEssentials/Data/ContiguousBytes.swift index 83f57d77d..47907414f 100644 --- a/Sources/FoundationEssentials/Data/ContiguousBytes.swift +++ b/Sources/FoundationEssentials/Data/ContiguousBytes.swift @@ -255,7 +255,9 @@ extension Slice : ContiguousBytes where Base : ContiguousBytes { //===--- Span Conformances -----------------------------------------===// @available(FoundationPreview 6.3, *) -extension RawSpan: ContiguousBytes { +extension RawSpan: ContiguousBytes { } + +extension RawSpan { @_alwaysEmitIntoClient public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { return try body(self) @@ -263,7 +265,9 @@ extension RawSpan: ContiguousBytes { } @available(FoundationPreview 6.3, *) -extension MutableRawSpan: ContiguousBytes { +extension MutableRawSpan: ContiguousBytes { } + +extension MutableRawSpan { @_alwaysEmitIntoClient public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { return try body(bytes) @@ -271,7 +275,9 @@ extension MutableRawSpan: ContiguousBytes { } @available(FoundationPreview 6.3, *) -extension OutputRawSpan: ContiguousBytes { +extension OutputRawSpan: ContiguousBytes { } + +extension OutputRawSpan { @_alwaysEmitIntoClient public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { try bytes.withUnsafeBytes(body) @@ -284,7 +290,10 @@ extension OutputRawSpan: ContiguousBytes { } @available(FoundationInlineArray 6.3, *) -extension UTF8Span: ContiguousBytes { +extension UTF8Span: ContiguousBytes { } + +@available(FoundationInlineArray 6.2, *) +extension UTF8Span { @_alwaysEmitIntoClient public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { try span.withUnsafeBytes(body) @@ -297,7 +306,9 @@ extension UTF8Span: ContiguousBytes { } @available(FoundationPreview 6.3, *) -extension Span: ContiguousBytes where Element == UInt8 { +extension Span: ContiguousBytes where Element == UInt8 { } + +extension Span where Element == UInt8 { @_alwaysEmitIntoClient public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { try body(bytes) @@ -305,7 +316,9 @@ extension Span: ContiguousBytes where Element == UInt8 { } @available(FoundationPreview 6.3, *) -extension MutableSpan: ContiguousBytes where Element == UInt8 { +extension MutableSpan: ContiguousBytes where Element == UInt8 { } + +extension MutableSpan where Element == UInt8 { @_alwaysEmitIntoClient public func withBytes(_ body: (RawSpan) throws(E) -> R) throws(E) -> R { try body(bytes) @@ -313,7 +326,9 @@ extension MutableSpan: ContiguousBytes where Element == UInt8 { } @available(FoundationPreview 6.3, *) -extension OutputSpan: ContiguousBytes where Element == UInt8 { +extension OutputSpan: ContiguousBytes where Element == UInt8 { } + +extension OutputSpan where Element == UInt8 { @_alwaysEmitIntoClient public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { try span.withUnsafeBytes(body) @@ -326,7 +341,10 @@ extension OutputSpan: ContiguousBytes where Element == UInt8 { } @available(FoundationInlineArray 6.3, *) -extension InlineArray: ContiguousBytes where Element == UInt8 { +extension InlineArray: ContiguousBytes where Element == UInt8 { } + +@available(FoundationInlineArray 6.2, *) +extension InlineArray where Element == UInt8 { @_alwaysEmitIntoClient public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R { return try span.withUnsafeBytes(body)