Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
276 changes: 261 additions & 15 deletions Sources/FoundationEssentials/Data/ContiguousBytes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,57 @@
//===--- 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 {
public protocol ContiguousBytes: ~Escapable, ~Copyable {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just confirming, are there any ABI implications on this change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it is not ABI-breaking (or source-breaking) to make this change.

#if !hasFeature(Embedded)
/// 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<R>(_ 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<R, E>(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R
#endif

/// Calls the given closure with the contents of underlying storage.
///
/// - note: Calling `withBytes` multiple times does not guarantee that
/// the same span will be passed in every time.
@available(FoundationPreview 6.3, *)
func withBytes<R, E>(_ 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 `withBytes` multiple times does not guarantee that
/// the same span will be passed in every time.
@_alwaysEmitIntoClient
public func withBytes<R, E>(_ body: (RawSpan) throws(E) -> R) throws(E) -> R {
#if !hasFeature(Embedded)
do {
return try withUnsafeBytes { (buffer) in
try body(buffer.bytes)
}
} catch let error {
// Note: withUnsafeBytes is rethrowing, so we have an "any Error" here that needs casting.
throw error as! E
}
#else
return try withUnsafeBytes { (buffer) throws(E) in
try body(buffer.bytes)
}
#endif
}
}

//===--- Collection Conformances ------------------------------------------===//

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the array types implement withBytes(_:) instead of using the default implementation?

Expand All @@ -43,69 +84,274 @@ 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<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
#if !hasFeature(Embedded)
// Historical ABI
@usableFromInline
@abi(func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R)
func __abi__withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
return try body(self)
}
#endif

@_alwaysEmitIntoClient
public func withUnsafeBytes<R, E>(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R {
return try body(self)
}

@_alwaysEmitIntoClient
public func withBytes<R: ~Copyable, E>(_ 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, *)
extension UnsafeMutableRawBufferPointer : ContiguousBytes {
@inlinable
public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
#if !hasFeature(Embedded)
// Historical ABI
@usableFromInline
@abi(func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R)
func __abi__withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
return try body(UnsafeRawBufferPointer(self))
}
#endif

@_alwaysEmitIntoClient
public func withUnsafeBytes<R, E>(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R {
return try body(UnsafeRawBufferPointer(self))
}

@_alwaysEmitIntoClient
public func withBytes<R: ~Copyable, E>(_ body: (RawSpan) throws(E) -> R) throws(E) -> R {
return try body(bytes)
}
}

// 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<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
#if !hasFeature(Embedded)
@usableFromInline
@abi(func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R)
func __abi__withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
return try body(UnsafeRawBufferPointer(self))
}
#endif

@_alwaysEmitIntoClient
public func withUnsafeBytes<R, E>(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R {
return try body(UnsafeRawBufferPointer(self))
}

@_alwaysEmitIntoClient
public func withBytes<R: ~Copyable, E>(_ body: (RawSpan) throws(E) -> R) throws(E) -> R {
return try body(span.bytes)
}
}

// 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<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
#if !hasFeature(Embedded)
@usableFromInline
@abi(func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R)
func __abi__withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
return try body(UnsafeRawBufferPointer(self))
}
#endif

@_alwaysEmitIntoClient
public func withUnsafeBytes<R, E>(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R {
return try body(UnsafeRawBufferPointer(self))
}

@_alwaysEmitIntoClient
public func withBytes<R: ~Copyable, E>(_ body: (RawSpan) throws(E) -> R) throws(E) -> R {
return try body(span.bytes)
}
}

// 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<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
#if !hasFeature(Embedded)
@usableFromInline
@abi(func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R)
func __abi__withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
return try body(UnsafeRawBufferPointer(start: nil, count: 0))
}
#endif

@_alwaysEmitIntoClient
public func withUnsafeBytes<R, E>(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R {
return try body(UnsafeRawBufferPointer(start: nil, count: 0))
}
}

// 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<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
#if !hasFeature(Embedded)
@usableFromInline
@abi(func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R)
func __abi__withUnsafeBytes<R>(_ 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<R, E>(_ 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<ResultType>(_ body: (UnsafeRawBufferPointer) throws -> ResultType) rethrows -> ResultType {
#if !hasFeature(Embedded)
@usableFromInline
@abi(func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R)
func __abi__withUnsafeBytes<ResultType>(_ 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)
let sliceBuffer = UnsafeRawBufferPointer(start: slicePtr, count: self.count)
return try body(sliceBuffer)
}
}
#endif

@_alwaysEmitIntoClient
public func withUnsafeBytes<ResultType, ErrorType>(_ 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) in
let slicePtr = ptr.baseAddress?.advanced(by: offset)
let sliceBuffer = UnsafeRawBufferPointer(start: slicePtr, count: self.count)
return try body(sliceBuffer)
}
} catch let error {
// Note: withUnsafeBytes is rethrowing, so we have an "any Error" here that needs casting.
throw error as! ErrorType
}
#else
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
}
}

//===--- Span Conformances -----------------------------------------===//

@available(FoundationPreview 6.3, *)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a public conformance, right? We would need an API review.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Here's the proposal PR: #1582

extension RawSpan: ContiguousBytes { }

extension RawSpan {
@_alwaysEmitIntoClient
public func withBytes<R: ~Copyable, E>(_ body: (RawSpan) throws(E) -> R) throws(E) -> R {
return try body(self)
}
}

@available(FoundationPreview 6.3, *)
extension MutableRawSpan: ContiguousBytes { }

extension MutableRawSpan {
@_alwaysEmitIntoClient
public func withBytes<R: ~Copyable, E>(_ body: (RawSpan) throws(E) -> R) throws(E) -> R {
return try body(bytes)
}
}

@available(FoundationPreview 6.3, *)
extension OutputRawSpan: ContiguousBytes { }

extension OutputRawSpan {
@_alwaysEmitIntoClient
public func withUnsafeBytes<R, E>(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R {
try bytes.withUnsafeBytes(body)
}

@_alwaysEmitIntoClient
public func withBytes<R: ~Copyable, E>(_ body: (RawSpan) throws(E) -> R) throws(E) -> R {
try body(bytes)
}
}

@available(FoundationInlineArray 6.3, *)
extension UTF8Span: ContiguousBytes { }

@available(FoundationInlineArray 6.2, *)
extension UTF8Span {
@_alwaysEmitIntoClient
public func withUnsafeBytes<R, E>(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R {
try span.withUnsafeBytes(body)
}

@_alwaysEmitIntoClient
public func withBytes<R: ~Copyable, E>(_ body: (RawSpan) throws(E) -> R) throws(E) -> R {
return try body(span.bytes)
}
}

@available(FoundationPreview 6.3, *)
extension Span: ContiguousBytes where Element == UInt8 { }

extension Span where Element == UInt8 {
@_alwaysEmitIntoClient
public func withBytes<R: ~Copyable, E>(_ body: (RawSpan) throws(E) -> R) throws(E) -> R {
try body(bytes)
}
}

@available(FoundationPreview 6.3, *)
extension MutableSpan: ContiguousBytes where Element == UInt8 { }

extension MutableSpan where Element == UInt8 {
@_alwaysEmitIntoClient
public func withBytes<R: ~Copyable, E>(_ body: (RawSpan) throws(E) -> R) throws(E) -> R {
try body(bytes)
}
}

@available(FoundationPreview 6.3, *)
extension OutputSpan: ContiguousBytes where Element == UInt8 { }

extension OutputSpan where Element == UInt8 {
@_alwaysEmitIntoClient
public func withUnsafeBytes<R, E>(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R {
try span.withUnsafeBytes(body)
}

@_alwaysEmitIntoClient
public func withBytes<R: ~Copyable, E>(_ body: (RawSpan) throws(E) -> R) throws(E) -> R {
try body(span.bytes)
}
}

@available(FoundationInlineArray 6.3, *)
extension InlineArray: ContiguousBytes where Element == UInt8 { }

@available(FoundationInlineArray 6.2, *)
extension InlineArray where Element == UInt8 {
@_alwaysEmitIntoClient
public func withUnsafeBytes<R, E>(_ body: (UnsafeRawBufferPointer) throws(E) -> R) throws(E) -> R {
return try span.withUnsafeBytes(body)
}

@_alwaysEmitIntoClient
public func withBytes<R: ~Copyable, E>(_ body: (RawSpan) throws(E) -> R) throws(E) -> R {
try body(span.bytes)
}
}
Loading