From 312d650628dbf047de4177fe23b23dea19afbbc2 Mon Sep 17 00:00:00 2001 From: Nate Cook Date: Wed, 18 Jun 2025 12:50:20 -0500 Subject: [PATCH] Make `ParserSpan` noncopyable This change helps communicate the intended usage of `ParserSpan`, though it doesn't strictly prevent creating a copy that represents the same memory. Specifically, the `seeking(...)` methods return copies of the span with different boundaries, to support separate parsing of different subregions of memory, and calling e.g. `seeking(toRelativeOffset: 0)` returns an exact copy of the parser span. With a few changes inside the library to handle these explicit copies, this change is fully source compatible with all the included example parsers, further showing that it matches the intended usage and semantics of the library. --- .../BinaryParsing/Parser Types/ParserSpan.swift | 14 +++++++++++--- Sources/BinaryParsing/Parser Types/Seeking.swift | 8 ++++---- Tests/BinaryParsingTests/SeekingTests.swift | 2 +- Tests/BinaryParsingTests/TestingSupport.swift | 2 +- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Sources/BinaryParsing/Parser Types/ParserSpan.swift b/Sources/BinaryParsing/Parser Types/ParserSpan.swift index a23b7bb..41ff61b 100644 --- a/Sources/BinaryParsing/Parser Types/ParserSpan.swift +++ b/Sources/BinaryParsing/Parser Types/ParserSpan.swift @@ -12,7 +12,7 @@ /// A non-owning, non-escaping view for parsing binary data. /// /// You can access a `ParserSpan` from a -public struct ParserSpan: ~Escapable, BitwiseCopyable { +public struct ParserSpan: ~Escapable, ~Copyable { @usableFromInline var _bytes: RawSpan @usableFromInline @@ -29,6 +29,14 @@ public struct ParserSpan: ~Escapable, BitwiseCopyable { self._upperBound = _bytes.byteCount } + @inlinable + @lifetime(copy other) + init(copying other: borrowing ParserSpan) { + self._bytes = other._bytes + self._lowerBound = other._lowerBound + self._upperBound = other._upperBound + } + @unsafe @inlinable @lifetime(borrow buffer) @@ -91,7 +99,7 @@ extension ParserSpan { mutating func divide(at index: Int) -> ParserSpan { precondition(index >= _lowerBound) precondition(index <= _upperBound) - var result = self + var result = ParserSpan(copying: self) result._upperBound = index self._lowerBound = index return result @@ -193,7 +201,7 @@ extension ParserSpan { _ body: (inout ParserSpan) throws(E) -> T ) throws(E) -> T { // Make a mutable copy to perform the work in `body`. - var copy = self + var copy = ParserSpan(copying: self) let result = try body(©) // `body` didn't throw, so update `self`. self = copy diff --git a/Sources/BinaryParsing/Parser Types/Seeking.swift b/Sources/BinaryParsing/Parser Types/Seeking.swift index cc52009..111b800 100644 --- a/Sources/BinaryParsing/Parser Types/Seeking.swift +++ b/Sources/BinaryParsing/Parser Types/Seeking.swift @@ -15,7 +15,7 @@ extension ParserSpan { public func seeking(toRange range: ParserRange) throws(ParsingError) -> ParserSpan { - var result = self + var result = ParserSpan(copying: self) try result.seek(toRange: range) return result } @@ -25,7 +25,7 @@ extension ParserSpan { public func seeking(toRelativeOffset offset: some FixedWidthInteger) throws(ParsingError) -> ParserSpan { - var result = self + var result = ParserSpan(copying: self) try result.seek(toRelativeOffset: offset) return result } @@ -35,7 +35,7 @@ extension ParserSpan { public func seeking(toAbsoluteOffset offset: some FixedWidthInteger) throws(ParsingError) -> ParserSpan { - var result = self + var result = ParserSpan(copying: self) try result.seek(toAbsoluteOffset: offset) return result } @@ -45,7 +45,7 @@ extension ParserSpan { public func seeking(toOffsetFromEnd offset: some FixedWidthInteger) throws(ParsingError) -> ParserSpan { - var result = self + var result = ParserSpan(copying: self) try result.seek(toOffsetFromEnd: offset) return result } diff --git a/Tests/BinaryParsingTests/SeekingTests.swift b/Tests/BinaryParsingTests/SeekingTests.swift index 15cbd4b..c494f6d 100644 --- a/Tests/BinaryParsingTests/SeekingTests.swift +++ b/Tests/BinaryParsingTests/SeekingTests.swift @@ -61,7 +61,7 @@ struct SeekingTests { // Absolute offset is always referent to original bounds var slice1 = try input.sliceSpan(byteCount: 4) - var slice2 = slice1 + var slice2 = try input.seeking(toRange: slice1.parserRange) #expect(slice1.startPosition == 2) #expect(slice1.count == 4) diff --git a/Tests/BinaryParsingTests/TestingSupport.swift b/Tests/BinaryParsingTests/TestingSupport.swift index 4d61594..2aa33fb 100644 --- a/Tests/BinaryParsingTests/TestingSupport.swift +++ b/Tests/BinaryParsingTests/TestingSupport.swift @@ -14,7 +14,7 @@ import Testing /// Returns a Boolean value indicating whether two parser spans are identical, /// representing the same subregion of the same span of memory. -func === (lhs: ParserSpan, rhs: ParserSpan) -> Bool { +func === (lhs: borrowing ParserSpan, rhs: borrowing ParserSpan) -> Bool { guard lhs.startPosition == rhs.startPosition, lhs.count == rhs.count else { return false }