diff --git a/Sources/FoundationEssentials/Data/Data.swift b/Sources/FoundationEssentials/Data/Data.swift index ddef89081..8c091da65 100644 --- a/Sources/FoundationEssentials/Data/Data.swift +++ b/Sources/FoundationEssentials/Data/Data.swift @@ -345,10 +345,19 @@ public struct Data : RandomAccessCollection, MutableCollection, RangeReplaceable } } - @inlinable // This is @inlinable as a generic, trivially forwarding function. - public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> ResultType) rethrows -> ResultType { - return try _representation.withUnsafeBytes(body) + @_alwaysEmitIntoClient + public func withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws(E) -> ResultType) throws(E) -> ResultType { + try _representation.withUnsafeBytes(body) + } + +#if FOUNDATION_FRAMEWORK + @abi(func withUnsafeBytes(_: (UnsafeRawBufferPointer) throws -> R) throws -> R) + @_spi(FoundationLegacyABI) + @usableFromInline + internal func _legacy_withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> ResultType) throws -> ResultType { + try withUnsafeBytes(body) } +#endif // FOUNDATION_FRAMEWORK @available(macOS 10.14.4, iOS 12.2, watchOS 5.2, tvOS 12.2, *) @_alwaysEmitIntoClient @@ -467,16 +476,27 @@ public struct Data : RandomAccessCollection, MutableCollection, RangeReplaceable } @_alwaysEmitIntoClient - public func withContiguousStorageIfAvailable(_ body: (_ buffer: UnsafeBufferPointer) throws -> ResultType) rethrows -> ResultType? { - return try _representation.withUnsafeBytes { - return try $0.withMemoryRebound(to: UInt8.self, body) + public func withContiguousStorageIfAvailable( + _ body: (_ buffer: UnsafeBufferPointer) throws(E) -> ResultType + ) throws(E) -> ResultType? { + try _representation.withUnsafeBytes { bytes throws(E) in + try bytes.withMemoryRebound(to: UInt8.self, body) } } - @inlinable // This is @inlinable as a generic, trivially forwarding function. - public mutating func withUnsafeMutableBytes(_ body: (UnsafeMutableRawBufferPointer) throws -> ResultType) rethrows -> ResultType { - return try _representation.withUnsafeMutableBytes(body) + @_alwaysEmitIntoClient + public mutating func withUnsafeMutableBytes(_ body: (UnsafeMutableRawBufferPointer) throws(E) -> ResultType) throws(E) -> ResultType { + try _representation.withUnsafeMutableBytes(body) + } + +#if FOUNDATION_FRAMEWORK + @abi(mutating func withUnsafeMutableBytes(_: (UnsafeMutableRawBufferPointer) throws -> R) throws -> R) + @_spi(FoundationLegacyABI) + @usableFromInline + internal mutating func _legacy_withUnsafeMutableBytes(_ body: (UnsafeMutableRawBufferPointer) throws -> ResultType) throws -> ResultType { + try withUnsafeMutableBytes(body) } +#endif // FOUNDATION_FRAMEWORK // MARK: - diff --git a/Sources/FoundationEssentials/Data/Representations/Data+Inline.swift b/Sources/FoundationEssentials/Data/Representations/Data+Inline.swift index 22d4bf0a9..6cec361d2 100644 --- a/Sources/FoundationEssentials/Data/Representations/Data+Inline.swift +++ b/Sources/FoundationEssentials/Data/Representations/Data+Inline.swift @@ -126,22 +126,38 @@ extension Data { return count } - @inlinable // This is @inlinable as a generic, trivially forwarding function. - func withUnsafeBytes(_ apply: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result { - let count = Int(length) - return try Swift.withUnsafeBytes(of: bytes) { (rawBuffer) throws -> Result in - return try apply(UnsafeRawBufferPointer(start: rawBuffer.baseAddress, count: count)) + @_alwaysEmitIntoClient + func withUnsafeBytes(_ apply: (UnsafeRawBufferPointer) throws(E) -> Result) throws(E) -> Result { + try Swift.withUnsafeBytes(of: bytes) { [count = Int(length)] (rawBuffer) throws(E) -> Result in + try apply(UnsafeRawBufferPointer(start: rawBuffer.baseAddress, count: count)) } } - - @inlinable // This is @inlinable as a generic, trivially forwarding function. - mutating func withUnsafeMutableBytes(_ apply: (UnsafeMutableRawBufferPointer) throws -> Result) rethrows -> Result { - let count = Int(length) - return try Swift.withUnsafeMutableBytes(of: &bytes) { (rawBuffer) throws -> Result in - return try apply(UnsafeMutableRawBufferPointer(start: rawBuffer.baseAddress, count: count)) + +#if FOUNDATION_FRAMEWORK + @abi(func withUnsafeBytes(_: (UnsafeRawBufferPointer) throws -> R) throws -> R) + @_spi(FoundationLegacyABI) + @usableFromInline + internal func _legacy_withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> ResultType) throws -> ResultType { + try withUnsafeBytes(body) + } +#endif // FOUNDATION_FRAMEWORK + + @_alwaysEmitIntoClient + mutating func withUnsafeMutableBytes(_ apply: (UnsafeMutableRawBufferPointer) throws(E) -> Result) throws(E) -> Result { + try Swift.withUnsafeMutableBytes(of: &bytes) { [count = Int(length)] (rawBuffer) throws(E) -> Result in + try apply(UnsafeMutableRawBufferPointer(start: rawBuffer.baseAddress, count: count)) } } - + +#if FOUNDATION_FRAMEWORK + @abi(mutating func withUnsafeMutableBytes(_: (UnsafeMutableRawBufferPointer) throws -> R) throws -> R) + @_spi(FoundationLegacyABI) + @usableFromInline + internal mutating func _legacy_withUnsafeMutableBytes(_ body: (UnsafeMutableRawBufferPointer) throws -> ResultType) throws -> ResultType { + try withUnsafeMutableBytes(body) + } +#endif // FOUNDATION_FRAMEWORK + @inlinable // This is @inlinable as trivially computable. mutating func append(byte: UInt8) { let count = self.count diff --git a/Sources/FoundationEssentials/Data/Representations/Data+InlineSlice.swift b/Sources/FoundationEssentials/Data/Representations/Data+InlineSlice.swift index 0e2abd7e6..6a9a6fd7d 100644 --- a/Sources/FoundationEssentials/Data/Representations/Data+InlineSlice.swift +++ b/Sources/FoundationEssentials/Data/Representations/Data+InlineSlice.swift @@ -157,17 +157,35 @@ extension Data { } } - @inlinable // This is @inlinable as a generic, trivially forwarding function. - func withUnsafeBytes(_ apply: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result { - return try storage.withUnsafeBytes(in: range, apply: apply) + @_alwaysEmitIntoClient + func withUnsafeBytes(_ apply: (UnsafeRawBufferPointer) throws(E) -> Result) throws(E) -> Result { + try storage.withUnsafeBytes(in: range, apply: apply) } - - @inlinable // This is @inlinable as a generic, trivially forwarding function. - mutating func withUnsafeMutableBytes(_ apply: (UnsafeMutableRawBufferPointer) throws -> Result) rethrows -> Result { + +#if FOUNDATION_FRAMEWORK + @abi(func withUnsafeBytes(_: (UnsafeRawBufferPointer) throws -> R) throws -> R) + @_spi(FoundationLegacyABI) + @usableFromInline + internal func _legacy_withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> ResultType) throws -> ResultType { + try withUnsafeBytes(body) + } +#endif // FOUNDATION_FRAMEWORK + + @_alwaysEmitIntoClient + mutating func withUnsafeMutableBytes(_ apply: (UnsafeMutableRawBufferPointer) throws(E) -> Result) throws(E) -> Result { ensureUniqueReference() return try storage.withUnsafeMutableBytes(in: range, apply: apply) } - + +#if FOUNDATION_FRAMEWORK + @abi(mutating func withUnsafeMutableBytes(_: (UnsafeMutableRawBufferPointer) throws -> R) throws -> R) + @_spi(FoundationLegacyABI) + @usableFromInline + internal mutating func _legacy_withUnsafeMutableBytes(_ body: (UnsafeMutableRawBufferPointer) throws -> ResultType) throws -> ResultType { + try withUnsafeMutableBytes(body) + } +#endif // FOUNDATION_FRAMEWORK + @inlinable // This is @inlinable as reasonably small. mutating func append(contentsOf buffer: UnsafeRawBufferPointer) { assert(endIndex + buffer.count < HalfInt.max) diff --git a/Sources/FoundationEssentials/Data/Representations/Data+LargeSlice.swift b/Sources/FoundationEssentials/Data/Representations/Data+LargeSlice.swift index 2974d2921..a3752a8e0 100644 --- a/Sources/FoundationEssentials/Data/Representations/Data+LargeSlice.swift +++ b/Sources/FoundationEssentials/Data/Representations/Data+LargeSlice.swift @@ -145,17 +145,35 @@ extension Data { return slice.range } - @inlinable // This is @inlinable as a generic, trivially forwarding function. - func withUnsafeBytes(_ apply: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result { - return try storage.withUnsafeBytes(in: range, apply: apply) + @_alwaysEmitIntoClient + func withUnsafeBytes(_ apply: (UnsafeRawBufferPointer) throws(E) -> Result) throws(E) -> Result { + try storage.withUnsafeBytes(in: range, apply: apply) } - - @inlinable // This is @inlinable as a generic, trivially forwarding function. - mutating func withUnsafeMutableBytes(_ apply: (UnsafeMutableRawBufferPointer) throws -> Result) rethrows -> Result { + +#if FOUNDATION_FRAMEWORK + @abi(func withUnsafeBytes(_: (UnsafeRawBufferPointer) throws -> R) throws -> R) + @_spi(FoundationLegacyABI) + @usableFromInline + internal func _legacy_withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> ResultType) throws -> ResultType { + try withUnsafeBytes(body) + } +#endif // FOUNDATION_FRAMEWORK + + @_alwaysEmitIntoClient + mutating func withUnsafeMutableBytes(_ apply: (UnsafeMutableRawBufferPointer) throws(E) -> Result) throws(E) -> Result { ensureUniqueReference() return try storage.withUnsafeMutableBytes(in: range, apply: apply) } - + +#if FOUNDATION_FRAMEWORK + @abi(mutating func withUnsafeMutableBytes(_: (UnsafeMutableRawBufferPointer) throws -> R) throws -> R) + @_spi(FoundationLegacyABI) + @usableFromInline + internal mutating func _legacy_withUnsafeMutableBytes(_ body: (UnsafeMutableRawBufferPointer) throws -> ResultType) throws -> ResultType { + try withUnsafeMutableBytes(body) + } +#endif // FOUNDATION_FRAMEWORK + @inlinable // This is @inlinable as reasonably small. mutating func append(contentsOf buffer: UnsafeRawBufferPointer) { ensureUniqueReference() diff --git a/Sources/FoundationEssentials/Data/Representations/Data+Representation.swift b/Sources/FoundationEssentials/Data/Representations/Data+Representation.swift index a6de6d806..536df6186 100644 --- a/Sources/FoundationEssentials/Data/Representations/Data+Representation.swift +++ b/Sources/FoundationEssentials/Data/Representations/Data+Representation.swift @@ -213,8 +213,8 @@ extension Data { } } - @inlinable // This is @inlinable as a generic, trivially forwarding function. - func withUnsafeBytes(_ apply: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result { + @_alwaysEmitIntoClient + func withUnsafeBytes(_ apply: (UnsafeRawBufferPointer) throws(E) -> Result) throws(E) -> Result { switch self { case .empty: let empty = InlineData() @@ -227,9 +227,18 @@ extension Data { return try slice.withUnsafeBytes(apply) } } - - @inlinable // This is @inlinable as a generic, trivially forwarding function. - mutating func withUnsafeMutableBytes(_ apply: (UnsafeMutableRawBufferPointer) throws -> Result) rethrows -> Result { + +#if FOUNDATION_FRAMEWORK + @abi(func withUnsafeBytes(_: (UnsafeRawBufferPointer) throws -> R) throws -> R) + @_spi(FoundationLegacyABI) + @usableFromInline + internal func _legacy_withUnsafeBytes(_ body: (UnsafeRawBufferPointer) throws -> ResultType) throws -> ResultType { + try withUnsafeBytes(body) + } +#endif // FOUNDATION_FRAMEWORK + + @_alwaysEmitIntoClient + mutating func withUnsafeMutableBytes(_ apply: (UnsafeMutableRawBufferPointer) throws(E) -> Result) throws(E) -> Result { switch self { case .empty: var empty = InlineData() @@ -247,7 +256,16 @@ extension Data { return try slice.withUnsafeMutableBytes(apply) } } - + +#if FOUNDATION_FRAMEWORK + @abi(mutating func withUnsafeMutableBytes(_: (UnsafeMutableRawBufferPointer) throws -> R) throws -> R) + @_spi(FoundationLegacyABI) + @usableFromInline + internal mutating func _legacy_withUnsafeMutableBytes(_ body: (UnsafeMutableRawBufferPointer) throws -> ResultType) throws -> ResultType { + try withUnsafeMutableBytes(body) + } +#endif // FOUNDATION_FRAMEWORK + @usableFromInline // This is not @inlinable as it is a non-trivial, non-generic function. func enumerateBytes(_ block: (_ buffer: UnsafeBufferPointer, _ byteIndex: Index, _ stop: inout Bool) -> Void) { switch self { diff --git a/Sources/FoundationEssentials/Data/Representations/DataStorage.swift b/Sources/FoundationEssentials/Data/Representations/DataStorage.swift index 146da906a..bbe5c46fc 100644 --- a/Sources/FoundationEssentials/Data/Representations/DataStorage.swift +++ b/Sources/FoundationEssentials/Data/Representations/DataStorage.swift @@ -97,18 +97,34 @@ internal final class __DataStorage : @unchecked Sendable { return UnsafeRawPointer(_bytes)?.advanced(by: -_offset) } - @inlinable // This is @inlinable despite escaping the _DataStorage boundary layer because it is generic and trivially forwarding. - @discardableResult - func withUnsafeBytes(in range: Range, apply: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result { - return try apply(UnsafeRawBufferPointer(start: _bytes?.advanced(by: range.lowerBound - _offset), count: Swift.min(range.upperBound - range.lowerBound, _length))) + @_alwaysEmitIntoClient + func withUnsafeBytes(in range: Range, apply: (UnsafeRawBufferPointer) throws(E) -> Result) throws(E) -> Result { + try apply(UnsafeRawBufferPointer(start: _bytes?.advanced(by: range.lowerBound - _offset), count: Swift.min(range.upperBound - range.lowerBound, _length))) } - - @inlinable // This is @inlinable despite escaping the _DataStorage boundary layer because it is generic and trivially forwarding. - @discardableResult - func withUnsafeMutableBytes(in range: Range, apply: (UnsafeMutableRawBufferPointer) throws -> Result) rethrows -> Result { - return try apply(UnsafeMutableRawBufferPointer(start: _bytes!.advanced(by:range.lowerBound - _offset), count: Swift.min(range.upperBound - range.lowerBound, _length))) + +#if FOUNDATION_FRAMEWORK + @abi(func withUnsafeBytes(in: Range, apply: (UnsafeRawBufferPointer) throws -> R) throws -> R) + @_spi(FoundationLegacyABI) + @usableFromInline + func _legacy_withUnsafeBytes(in range: Range, apply: (UnsafeRawBufferPointer) throws -> Result) throws -> Result { + try withUnsafeBytes(in: range, apply: apply) } - +#endif // FOUNDATION_FRAMEWORK + + @_alwaysEmitIntoClient + func withUnsafeMutableBytes(in range: Range, apply: (UnsafeMutableRawBufferPointer) throws(E) -> Result) throws(E) -> Result { + try apply(UnsafeMutableRawBufferPointer(start: _bytes!.advanced(by:range.lowerBound - _offset), count: Swift.min(range.upperBound - range.lowerBound, _length))) + } + +#if FOUNDATION_FRAMEWORK + @abi(func withUnsafeMutableBytes(in: Range, apply: (UnsafeMutableRawBufferPointer) throws -> R) throws -> R) + @_spi(FoundationLegacyABI) + @usableFromInline + internal func _legacy_withUnsafeMutableBytes(in range: Range, apply: (UnsafeMutableRawBufferPointer) throws -> ResultType) throws -> ResultType { + try withUnsafeMutableBytes(in: range, apply: apply) + } +#endif // FOUNDATION_FRAMEWORK + @inlinable // This is @inlinable as trivially computable. var mutableBytes: UnsafeMutableRawPointer? { return _bytes?.advanced(by: _offset &* -1) // _offset is guaranteed to be non-negative, so it can never overflow when negating diff --git a/Tests/FoundationEssentialsTests/DataLegacyABITests.swift b/Tests/FoundationEssentialsTests/DataLegacyABITests.swift new file mode 100644 index 000000000..79bd08a40 --- /dev/null +++ b/Tests/FoundationEssentialsTests/DataLegacyABITests.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#if FOUNDATION_FRAMEWORK +@_spi(FoundationLegacyABI) @testable import Foundation +import Testing + +@Suite("Foundation Legacy ABI") +private final class FoundationLegacyABITests { + + @Test func validateDataLegacyABI() { + var data = Data() + + data._legacy_withUnsafeBytes { _ in } + data._legacy_withUnsafeMutableBytes { _ in } + } +} + +#endif // FOUNDATION_FRAMEWORK diff --git a/Tests/FoundationEssentialsTests/DataTests.swift b/Tests/FoundationEssentialsTests/DataTests.swift index 0bab5ee15..0a7719612 100644 --- a/Tests/FoundationEssentialsTests/DataTests.swift +++ b/Tests/FoundationEssentialsTests/DataTests.swift @@ -1484,6 +1484,67 @@ private final class DataTests { } } + private struct Value: ~Copyable { + var stored: Int + init(_ value: Int) { stored = value } + } + + private enum LocalError: Error, Equatable { case error } + + @Test func validateGeneralizedParameters_withUnsafeBytes() { + var data: Data + + data = Data(repeating: 2, count: 12) + let value1 = data.withUnsafeBytes { + let sum = $0.withMemoryRebound(to: UInt8.self) { Int($0.reduce(0,+)) } + return Value(sum) + } + #expect(value1.stored == 24) + #expect(throws: LocalError.error) { + try data.withUnsafeBytes { _ throws(LocalError) in throw(LocalError.error) } + } + + data = Data(repeating: 1, count: 128) + let value2 = data.withUnsafeBytes { + let sum = $0.withMemoryRebound(to: UInt8.self) { Int($0.reduce(0,+)) } + return Value(sum) + } + #expect(value2.stored == 128) + #expect(throws: LocalError.error) { + try data.withUnsafeBytes { _ throws(LocalError) in throw(LocalError.error) } + } + } + + @Test func validateGeneralizedParameters_withUnsafeMutableBytes() { + var data: Data + + data = Data(count: 12) + let value1 = data.withUnsafeMutableBytes { + $0.withMemoryRebound(to: UInt8.self) { + for i in $0.indices { $0[i] = 2 } + } + let sum = $0.withMemoryRebound(to: UInt8.self) { Int($0.reduce(0,+)) } + return Value(sum) + } + #expect(value1.stored == 24) + #expect(throws: LocalError.error) { + try data.withUnsafeBytes { _ throws(LocalError) in throw(LocalError.error) } + } + + data = Data(count: 128) + let value2 = data.withUnsafeMutableBytes { + $0.withMemoryRebound(to: UInt8.self) { + for i in $0.indices { $0[i] = 1 } + } + let sum = $0.withMemoryRebound(to: UInt8.self) { Int($0.reduce(0,+)) } + return Value(sum) + } + #expect(value2.stored == 128) + #expect(throws: LocalError.error) { + try data.withUnsafeBytes { _ throws(LocalError) in throw(LocalError.error) } + } + } + @Test func sliceHash() { let base1 = Data([0, 0xFF, 0xFF, 0]) let base2 = Data([0, 0xFF, 0xFF, 0]) @@ -1767,7 +1828,7 @@ private final class DataTests { let byteCount = span.byteCount #expect(byteCount == count) let v = UInt8.random(in: 10..<100) - var sub = span.extracting(i..