Skip to content

Commit cba000e

Browse files
authored
Revise the typed throws design (swiftlang#10)
This changes the conventions for typed throws for the parser initializers that used the conditionally-defined `ThrownParsingError` type alias. Some of the parsers didn't actually need that alias, and instead could use a function-specific generic error. For others parsers, the library now provides two overloads: one with `ParsingError`-typed errors and one with untyped errors, which is omitted in embedded contexts. Since it is more specific, the typed- error overload is chosen when a passed-in closure either does not throw or throws only `ParsingError`s.
1 parent c808031 commit cba000e

File tree

5 files changed

+231
-104
lines changed

5 files changed

+231
-104
lines changed

Sources/BinaryParsing/Parser Types/ParserSource.swift

Lines changed: 88 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ public protocol ExpressibleByParsing {
1919
}
2020

2121
extension ExpressibleByParsing {
22+
@_alwaysEmitIntoClient
23+
public init(
24+
parsing data: some ParserSpanProvider
25+
) throws(ThrownParsingError) {
26+
self = try data.withParserSpan(Self.init(parsing:))
27+
}
28+
29+
@_alwaysEmitIntoClient
30+
@_disfavoredOverload
2231
public init(parsing data: some RandomAccessCollection<UInt8>)
2332
throws(ThrownParsingError)
2433
{
@@ -41,112 +50,112 @@ extension RandomAccessCollection<UInt8> {
4150
) throws(ThrownParsingError) -> T? {
4251
#if canImport(Foundation)
4352
if let data = self as? Foundation.Data {
44-
do {
45-
return try data.withUnsafeBytes { buffer -> T in
46-
var span = ParserSpan(_unsafeBytes: buffer)
47-
return try body(&span)
48-
}
49-
} catch {
50-
// Workaround for lack of typed-throwing API on Data
51-
// swift-format-ignore: NeverForceUnwrap
52-
throw error as! ThrownParsingError
53+
let result = data.withUnsafeBytes { buffer in
54+
var span = ParserSpan(_unsafeBytes: buffer)
55+
return Result<T, ThrownParsingError> { try body(&span) }
56+
}
57+
switch result {
58+
case .success(let t): return t
59+
case .failure(let e): throw e
5360
}
5461
}
5562
#endif
56-
do {
57-
return try self.withContiguousStorageIfAvailable { buffer in
58-
let rawBuffer = UnsafeRawBufferPointer(buffer)
59-
var span = ParserSpan(_unsafeBytes: rawBuffer)
60-
return try body(&span)
61-
}
62-
} catch {
63-
// Workaround for lack of typed-throwing API on Collection
64-
// swift-format-ignore: NeverForceUnwrap
65-
throw error as! ThrownParsingError
63+
64+
let result = self.withContiguousStorageIfAvailable { buffer in
65+
let rawBuffer = UnsafeRawBufferPointer(buffer)
66+
var span = ParserSpan(_unsafeBytes: rawBuffer)
67+
return Result<T, ThrownParsingError> { try body(&span) }
68+
}
69+
switch result {
70+
case .success(let t): return t
71+
case .failure(let e): throw e
72+
case nil: return nil
6673
}
6774
}
6875
}
6976

7077
// MARK: ParserSpanProvider
7178

7279
public protocol ParserSpanProvider {
73-
func withParserSpan<T>(
74-
_ body: (inout ParserSpan) throws(ThrownParsingError) -> T
75-
) throws(ThrownParsingError) -> T
80+
func withParserSpan<T, E>(
81+
_ body: (inout ParserSpan) throws(E) -> T
82+
) throws(E) -> T
7683
}
7784

78-
#if canImport(Foundation)
79-
extension Data: ParserSpanProvider {
85+
extension ParserSpanProvider {
86+
#if !$Embedded
87+
@_alwaysEmitIntoClient
8088
@inlinable
8189
public func withParserSpan<T>(
82-
_ body: (inout ParserSpan) throws(ThrownParsingError) -> T
83-
) throws(ThrownParsingError) -> T {
84-
do {
85-
return try withUnsafeBytes { buffer -> T in
86-
// FIXME: RawSpan getter
87-
// var span = ParserSpan(buffer.bytes)
88-
var span = ParserSpan(_unsafeBytes: buffer)
89-
return try body(&span)
90-
}
91-
} catch {
92-
// Workaround for lack of typed-throwing API on Data
93-
// swift-format-ignore: NeverForceUnwrap
94-
throw error as! ThrownParsingError
90+
usingRange range: inout ParserRange,
91+
_ body: (inout ParserSpan) throws -> T
92+
) throws -> T {
93+
try withParserSpan { span in
94+
var subspan = try span.seeking(toRange: range)
95+
defer { range = subspan.parserRange }
96+
return try body(&subspan)
9597
}
9698
}
99+
#endif
97100

98101
@_alwaysEmitIntoClient
99102
@inlinable
100103
public func withParserSpan<T>(
101104
usingRange range: inout ParserRange,
102-
_ body: (inout ParserSpan) throws(ThrownParsingError) -> T
103-
) throws(ThrownParsingError) -> T {
104-
do {
105-
return try withUnsafeBytes { (buffer) throws(ThrownParsingError) -> T in
106-
// FIXME: RawSpan getter
107-
// var span = try ParserSpan(buffer.bytes)
108-
var span = try ParserSpan(_unsafeBytes: buffer)
109-
.seeking(toRange: range)
110-
defer {
111-
range = span.parserRange
112-
}
113-
return try body(&span)
114-
}
115-
} catch {
116-
// Workaround for lack of typed-throwing API on Data
117-
// swift-format-ignore: NeverForceUnwrap
118-
throw error as! ThrownParsingError
105+
_ body: (inout ParserSpan) throws(ParsingError) -> T
106+
) throws(ParsingError) -> T {
107+
try withParserSpan { (span) throws(ParsingError) in
108+
var subspan = try span.seeking(toRange: range)
109+
defer { range = subspan.parserRange }
110+
return try body(&subspan)
119111
}
120112
}
121113
}
122-
#endif
123114

124-
extension ParserSpanProvider where Self: RandomAccessCollection<UInt8> {
125-
@discardableResult
115+
#if canImport(Foundation)
116+
extension Data: ParserSpanProvider {
126117
@inlinable
127-
public func withParserSpan<T>(
128-
_ body: (inout ParserSpan) throws(ThrownParsingError) -> T
129-
) throws(ThrownParsingError) -> T {
130-
do {
131-
guard
132-
let result = try self.withContiguousStorageIfAvailable({ buffer in
133-
// FIXME: RawSpan getter
134-
// var span = ParserSpan(UnsafeRawBufferPointer(buffer).bytes)
135-
let rawBuffer = UnsafeRawBufferPointer(buffer)
136-
var span = ParserSpan(_unsafeBytes: rawBuffer)
137-
return try body(&span)
138-
})
139-
else {
140-
throw ParsingError(status: .userError, location: 0)
141-
}
142-
return result
143-
} catch {
144-
// Workaround for lack of typed-throwing API on Collection
145-
// swift-format-ignore: NeverForceUnwrap
146-
throw error as! ThrownParsingError
118+
public func withParserSpan<T, E>(
119+
_ body: (inout ParserSpan) throws(E) -> T
120+
) throws(E) -> T {
121+
let result = withUnsafeBytes { buffer in
122+
var span = ParserSpan(_unsafeBytes: buffer)
123+
return Result<T, E> { () throws(E) in try body(&span) }
124+
}
125+
switch result {
126+
case .success(let t): return t
127+
case .failure(let e): throw e
128+
}
129+
}
130+
}
131+
#endif
132+
133+
extension [UInt8]: ParserSpanProvider {
134+
public func withParserSpan<T, E>(
135+
_ body: (inout ParserSpan) throws(E) -> T
136+
) throws(E) -> T {
137+
let result = self.withUnsafeBytes { rawBuffer in
138+
var span = ParserSpan(_unsafeBytes: rawBuffer)
139+
return Result<T, E> { () throws(E) in try body(&span) }
140+
}
141+
switch result {
142+
case .success(let t): return t
143+
case .failure(let e): throw e
147144
}
148145
}
149146
}
150147

151-
extension [UInt8]: ParserSpanProvider {}
152-
extension ArraySlice<UInt8>: ParserSpanProvider {}
148+
extension ArraySlice<UInt8>: ParserSpanProvider {
149+
public func withParserSpan<T, E>(
150+
_ body: (inout ParserSpan) throws(E) -> T
151+
) throws(E) -> T {
152+
let result = self.withUnsafeBytes { rawBuffer in
153+
var span = ParserSpan(_unsafeBytes: rawBuffer)
154+
return Result<T, E> { () throws(E) in try body(&span) }
155+
}
156+
switch result {
157+
case .success(let t): return t
158+
case .failure(let e): throw e
159+
}
160+
}
161+
}

Sources/BinaryParsing/Parser Types/ParsingError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ extension ParsingError.Status: CustomStringConvertible {
121121
/// In a build for embedded Swift, `ThrownParsingError` instead aliases the
122122
/// specific `ParsingError` type. Because embedded Swift supports only
123123
/// fully-typed throws, and not the existential `any Error`, this allows you
124-
/// to still take use error-throwing APIs in an embedded context.
124+
/// to still use error-throwing APIs in an embedded context.
125125
public typealias ThrownParsingError = any Error
126126
#else
127127
// Documentation is built using the non-embedded build.

Sources/BinaryParsing/Parsers/Array.swift

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,14 @@ extension Array where Element == UInt8 {
3434
}
3535

3636
extension Array {
37+
#if !$Embedded
3738
@inlinable
3839
@lifetime(&input)
3940
public init(
4041
parsing input: inout ParserSpan,
4142
count: some FixedWidthInteger,
42-
parser: (inout ParserSpan) throws(ThrownParsingError) -> Element
43-
) throws(ThrownParsingError) {
43+
parser: (inout ParserSpan) throws -> Element
44+
) throws {
4445
let count = try Int(throwingOnOverflow: count)
4546
self = []
4647
self.reserveCapacity(count)
@@ -50,13 +51,30 @@ extension Array {
5051
try self.append(parser(&input))
5152
}
5253
}
54+
#endif
5355

5456
@inlinable
5557
@lifetime(&input)
56-
public init(
58+
public init<E>(
59+
parsing input: inout ParserSpan,
60+
count: Int,
61+
parser: (inout ParserSpan) throws(E) -> Element
62+
) throws(E) {
63+
self = []
64+
self.reserveCapacity(count)
65+
// This doesn't throw (e.g. on empty) because `parser` can produce valid
66+
// values no matter the state of `input`.
67+
for _ in 0..<count {
68+
try self.append(parser(&input))
69+
}
70+
}
71+
72+
@inlinable
73+
@lifetime(&input)
74+
public init<E>(
5775
parsingAll input: inout ParserSpan,
58-
parser: (inout ParserSpan) throws(ThrownParsingError) -> Element
59-
) throws(ThrownParsingError) {
76+
parser: (inout ParserSpan) throws(E) -> Element
77+
) throws(E) {
6078
self = []
6179
while !input.isEmpty {
6280
try self.append(parser(&input))

0 commit comments

Comments
 (0)