Skip to content

Adopt typed throws across 'BinaryParsing' #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 11, 2025
Merged
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
4 changes: 2 additions & 2 deletions Sources/BinaryParsing/Macros/MagicNumber.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func _loadAndCheckDirectBytes<
>(
parsing input: inout ParserSpan,
bigEndianValue: T
) throws {
) throws(ParsingError) {
let loadedValue = try T(parsingBigEndian: &input)
guard loadedValue == bigEndianValue else {
throw ParsingError(
Expand All @@ -29,7 +29,7 @@ func _loadAndCheckDirectBytesByteOrder<
>(
parsing input: inout ParserSpan,
bigEndianValue: T
) throws -> Endianness {
) throws(ParsingError) -> Endianness {
let loadedValue = try T(parsingBigEndian: &input)
if loadedValue == bigEndianValue {
return .big
Expand Down
2 changes: 1 addition & 1 deletion Sources/BinaryParsing/Operations/Optionators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ extension Optional where Wrapped: Comparable {
guard lhs <= rhs else { return nil }
return lhs..<rhs
}

@inlinable @inline(__always)
public static func ...? (lhs: Self, rhs: Self) -> ClosedRange<Wrapped>? {
guard let lhs, let rhs else { return nil }
Expand Down
43 changes: 30 additions & 13 deletions Sources/BinaryParsing/Operations/ThrowingOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
extension Collection {
@inlinable
public subscript(throwing i: Index) -> Element {
get throws {
get throws(ParsingError) {
guard (startIndex..<endIndex).contains(i) else {
throw ParsingError(statusOnly: .invalidValue)
}
Expand All @@ -24,7 +24,7 @@ extension Collection {
extension Optional {
@inlinable
public var unwrapped: Wrapped {
get throws {
get throws(ParsingError) {
switch self {
case .some(let v): return v
case .none:
Expand All @@ -36,7 +36,8 @@ extension Optional {

extension BinaryInteger {
@inlinable
public init(throwingOnOverflow other: some BinaryInteger) throws {
public init(throwingOnOverflow other: some BinaryInteger) throws(ParsingError)
{
guard let newValue = Self(exactly: other) else {
throw ParsingError(statusOnly: .invalidValue)
}
Expand All @@ -48,7 +49,9 @@ extension FixedWidthInteger {
// MARK: Nonmutating arithmetic

@inlinable
public func addingThrowingOnOverflow(_ other: Self) throws -> Self {
public func addingThrowingOnOverflow(_ other: Self) throws(ParsingError)
-> Self
{
let (result, overflow) = addingReportingOverflow(other)
if overflow {
throw ParsingError(statusOnly: .invalidValue)
Expand All @@ -57,7 +60,9 @@ extension FixedWidthInteger {
}

@inlinable
public func subtractingThrowingOnOverflow(_ other: Self) throws -> Self {
public func subtractingThrowingOnOverflow(_ other: Self) throws(ParsingError)
-> Self
{
let (result, overflow) = subtractingReportingOverflow(other)
if overflow {
throw ParsingError(statusOnly: .invalidValue)
Expand All @@ -66,7 +71,9 @@ extension FixedWidthInteger {
}

@inlinable
public func multipliedThrowingOnOverflow(by other: Self) throws -> Self {
public func multipliedThrowingOnOverflow(by other: Self) throws(ParsingError)
-> Self
{
let (result, overflow) = multipliedReportingOverflow(by: other)
if overflow {
throw ParsingError(statusOnly: .invalidValue)
Expand All @@ -75,7 +82,9 @@ extension FixedWidthInteger {
}

@inlinable
public func dividedThrowingOnOverflow(by other: Self) throws -> Self {
public func dividedThrowingOnOverflow(by other: Self) throws(ParsingError)
-> Self
{
let (result, overflow) = dividedReportingOverflow(by: other)
if overflow {
throw ParsingError(statusOnly: .invalidValue)
Expand All @@ -84,7 +93,8 @@ extension FixedWidthInteger {
}

@inlinable
public func remainderThrowingOnOverflow(dividingBy other: Self) throws -> Self
public func remainderThrowingOnOverflow(dividingBy other: Self)
throws(ParsingError) -> Self
{
let (result, overflow) = remainderReportingOverflow(dividingBy: other)
if overflow {
Expand All @@ -96,28 +106,35 @@ extension FixedWidthInteger {
// MARK: Mutating arithmetic

@inlinable
public mutating func addThrowingOnOverflow(_ other: Self) throws {
public mutating func addThrowingOnOverflow(_ other: Self) throws(ParsingError)
{
self = try self.addingThrowingOnOverflow(other)
}

@inlinable
public mutating func subtractThrowingOnOverflow(_ other: Self) throws {
public mutating func subtractThrowingOnOverflow(_ other: Self)
throws(ParsingError)
{
self = try self.subtractingThrowingOnOverflow(other)
}

@inlinable
public mutating func multiplyThrowingOnOverflow(by other: Self) throws {
public mutating func multiplyThrowingOnOverflow(by other: Self)
throws(ParsingError)
{
self = try self.multipliedThrowingOnOverflow(by: other)
}

@inlinable
public mutating func divideThrowingOnOverflow(by other: Self) throws {
public mutating func divideThrowingOnOverflow(by other: Self)
throws(ParsingError)
{
self = try self.dividedThrowingOnOverflow(by: other)
}

@inlinable
public mutating func formRemainderThrowingOnOverflow(dividingBy other: Self)
throws
throws(ParsingError)
{
self = try self.remainderThrowingOnOverflow(dividingBy: other)
}
Expand Down
5 changes: 3 additions & 2 deletions Sources/BinaryParsing/Parser Types/ParserRange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public struct ParserRange: Hashable {
}

extension ParserRange {
public func slicing<C: Collection<UInt8>>(_ coll: C) throws -> C.SubSequence
public func slicing<C: Collection<UInt8>>(_ coll: C) throws(ParsingError)
-> C.SubSequence
where C.Index == Int {
let validRange = coll.startIndex...coll.endIndex
guard validRange.contains(range.lowerBound),
Expand All @@ -49,7 +50,7 @@ extension ParserRange {

extension RandomAccessCollection<UInt8> where Index == Int {
public subscript(_ range: ParserRange) -> SubSequence {
get throws {
get throws(ParsingError) {
let validRange = startIndex...endIndex
guard validRange.contains(range.lowerBound),
validRange.contains(range.upperBound)
Expand Down
130 changes: 81 additions & 49 deletions Sources/BinaryParsing/Parser Types/ParserSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@ public import Foundation

public protocol ExpressibleByParsing {
@lifetime(&input)
init(parsing input: inout ParserSpan) throws
init(parsing input: inout ParserSpan) throws(ThrownParsingError)
}

extension ExpressibleByParsing {
public init(parsing data: some RandomAccessCollection<UInt8>) throws {
public init(parsing data: some RandomAccessCollection<UInt8>)
throws(ThrownParsingError)
{
guard
let result = try data.withParserSpanIfAvailable({ span in
let result = try data.withParserSpanIfAvailable({
(span) throws(ThrownParsingError) in
try Self.init(parsing: &span)
})
else {
throw ParsingError(
status: .invalidValue,
location: 0,
message: "Provided data type does not support contiguous access.")
throw ParsingError(statusOnly: .invalidValue)
}
self = result
}
Expand All @@ -37,81 +37,113 @@ extension ExpressibleByParsing {
extension RandomAccessCollection<UInt8> {
@inlinable
public func withParserSpanIfAvailable<T>(
_ body: (inout ParserSpan) throws -> T
) throws -> T? {
_ body: (inout ParserSpan) throws(ThrownParsingError) -> T
) throws(ThrownParsingError) -> T? {
#if canImport(Foundation)
if let data = self as? Foundation.Data {
return try data.withUnsafeBytes { buffer -> T in
var span = ParserSpan(_unsafeBytes: buffer)
return try body(&span)
do {
return try data.withUnsafeBytes { buffer -> T in
var span = ParserSpan(_unsafeBytes: buffer)
return try body(&span)
}
} catch {
// Workaround for lack of typed-throwing API on Data
// swift-format-ignore: NeverForceUnwrap
throw error as! ThrownParsingError
}
}
#endif
return try self.withContiguousStorageIfAvailable { buffer in
let rawBuffer = UnsafeRawBufferPointer(buffer)
var span = ParserSpan(_unsafeBytes: rawBuffer)
return try body(&span)
do {
return try self.withContiguousStorageIfAvailable { buffer in
let rawBuffer = UnsafeRawBufferPointer(buffer)
var span = ParserSpan(_unsafeBytes: rawBuffer)
return try body(&span)
}
} catch {
// Workaround for lack of typed-throwing API on Collection
// swift-format-ignore: NeverForceUnwrap
throw error as! ThrownParsingError
}
}
}

// MARK: ParserSpanProvider

public protocol ParserSpanProvider {
func withParserSpan<T>(_ body: (inout ParserSpan) throws -> T) throws -> T
func withParserSpan<T>(
_ body: (inout ParserSpan) throws(ThrownParsingError) -> T
) throws(ThrownParsingError) -> T
}

#if canImport(Foundation)
extension Data: ParserSpanProvider {
@inlinable
public func withParserSpan<T>(_ body: (inout ParserSpan) throws -> T) throws
-> T
{
try withUnsafeBytes { buffer -> T in
// FIXME: RawSpan getter
// var span = ParserSpan(buffer.bytes)
var span = ParserSpan(_unsafeBytes: buffer)
return try body(&span)
public func withParserSpan<T>(
_ body: (inout ParserSpan) throws(ThrownParsingError) -> T
) throws(ThrownParsingError) -> T {
do {
return try withUnsafeBytes { buffer -> T in
// FIXME: RawSpan getter
// var span = ParserSpan(buffer.bytes)
var span = ParserSpan(_unsafeBytes: buffer)
return try body(&span)
}
} catch {
// Workaround for lack of typed-throwing API on Data
// swift-format-ignore: NeverForceUnwrap
throw error as! ThrownParsingError
}
}

@_alwaysEmitIntoClient
@inlinable
public func withParserSpan<T>(
usingRange range: inout ParserRange,
_ body: (inout ParserSpan) throws -> T
) rethrows -> T {
try withUnsafeBytes { buffer -> T in
// FIXME: RawSpan getter
// var span = try ParserSpan(buffer.bytes)
var span = try ParserSpan(_unsafeBytes: buffer)
.seeking(toRange: range)
defer {
range = span.parserRange
_ body: (inout ParserSpan) throws(ThrownParsingError) -> T
) throws(ThrownParsingError) -> T {
do {
return try withUnsafeBytes { (buffer) throws(ThrownParsingError) -> T in
// FIXME: RawSpan getter
// var span = try ParserSpan(buffer.bytes)
var span = try ParserSpan(_unsafeBytes: buffer)
.seeking(toRange: range)
defer {
range = span.parserRange
}
return try body(&span)
}
return try body(&span)
} catch {
// Workaround for lack of typed-throwing API on Data
// swift-format-ignore: NeverForceUnwrap
throw error as! ThrownParsingError
}
}
}
#endif

extension ParserSpanProvider where Self: RandomAccessCollection<UInt8> {
@inlinable
public func withParserSpan<T>(_ body: (inout ParserSpan) throws -> T) throws
-> T
{
guard
let result = try self.withContiguousStorageIfAvailable({ buffer in
// FIXME: RawSpan getter
// var span = ParserSpan(UnsafeRawBufferPointer(buffer).bytes)
let rawBuffer = UnsafeRawBufferPointer(buffer)
var span = ParserSpan(_unsafeBytes: rawBuffer)
return try body(&span)
})
else {
throw ParsingError(status: .userError, location: 0)
public func withParserSpan<T>(
_ body: (inout ParserSpan) throws(ThrownParsingError) -> T
) throws(ThrownParsingError) -> T {
do {
guard
let result = try self.withContiguousStorageIfAvailable({ buffer in
// FIXME: RawSpan getter
// var span = ParserSpan(UnsafeRawBufferPointer(buffer).bytes)
let rawBuffer = UnsafeRawBufferPointer(buffer)
var span = ParserSpan(_unsafeBytes: rawBuffer)
return try body(&span)
})
else {
throw ParsingError(status: .userError, location: 0)
}
return result
} catch {
// Workaround for lack of typed-throwing API on Collection
// swift-format-ignore: NeverForceUnwrap
throw error as! ThrownParsingError
}
return result
}
}

Expand Down
16 changes: 8 additions & 8 deletions Sources/BinaryParsing/Parser Types/ParserSpan.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ extension ParserSpan {
@_alwaysEmitIntoClient
@inlinable
@unsafe
public func withUnsafeBytes<T>(
_ body: (UnsafeRawBufferPointer) throws -> T
) rethrows -> T {
try _bytes.withUnsafeBytes { fullBuffer in
public func withUnsafeBytes<T, E>(
_ body: (UnsafeRawBufferPointer) throws(E) -> T
) throws(E) -> T {
try _bytes.withUnsafeBytes { (fullBuffer) throws(E) in
let buffer = UnsafeRawBufferPointer(
rebasing: fullBuffer[_lowerBound..<_upperBound])
return try body(buffer)
Expand All @@ -137,7 +137,7 @@ extension ParserSpan {
@usableFromInline
internal mutating func _divide(
atByteOffset count: some FixedWidthInteger
) throws -> ParserSpan {
) throws(ParsingError) -> ParserSpan {
guard let count = Int(exactly: count), count >= 0 else {
throw ParsingError(status: .invalidValue, location: startPosition)
}
Expand Down Expand Up @@ -186,9 +186,9 @@ extension ParserSpan {
/// `atomically` guarantees that the input span isn't modified in that case.
@inlinable
@lifetime(&self)
public mutating func atomically<T>(_ body: (inout ParserSpan) throws -> T)
rethrows -> T
{
public mutating func atomically<T, E>(
_ body: (inout ParserSpan) throws(E) -> T
) throws(E) -> T {
// Make a mutable copy to perform the work in `body`.
var copy = self
let result = try body(&copy)
Expand Down
Loading