Repository: apple/swift-nio
File: Sources/NIOCore/ByteBuffer-views.swift (Swift)

Code before:
extension ByteBufferView: RangeReplaceableCollection {
    // required by `RangeReplaceableCollection`
    @inlinable
    public init() {
        self = ByteBufferView(ByteBuffer())
    }

    /// Reserves enough space in the underlying `ByteBuffer` to store the specified
    /// number of bytes.
    ///
    /// See the documentation for ``ByteBuffer.reserveCapacity(_:)`` for more details.
    @inlinable
    public mutating func reserveCapacity(_ minimumCapacity: Int) {
        self._buffer.reserveCapacity(minimumCapacity)
    }

    /// Writes a single byte to the underlying `ByteBuffer`.
    @inlinable
    public mutating func append(_ byte: UInt8) {
        self._buffer.writeBytes(CollectionOfOne<UInt8>(byte))
    }

    /// Writes a sequence of bytes to the underlying `ByteBuffer`.
    @inlinable
    public mutating func append<Bytes: Sequence>(contentsOf bytes: Bytes) where Bytes.Element == UInt8 {
        self._buffer.writeBytes(bytes)
    }

    @inlinable
    public mutating func replaceSubrange<C: Collection>(_ subrange: Range<Index>, with newElements: C) where ByteBufferView.Element == C.Element {
        precondition(subrange.startIndex >= self.startIndex && subrange.endIndex <= self.endIndex,
                     "subrange out of bounds")

        if newElements.count == subrange.count {
            self._buffer.setBytes(newElements, at: subrange.startIndex)
        } else if newElements.count < subrange.count {
            // Replace the subrange.
            self._buffer.setBytes(newElements, at: subrange.startIndex)

            // Remove the unwanted bytes between the newly copied bytes and the end of the subrange.
            // try! is fine here: the copied range is within the view and the length can't be negative.
            try! self._buffer.copyBytes(at: subrange.endIndex,
                                        to: subrange.startIndex.advanced(by: newElements.count),
                                        length: subrange.endIndex.distance(to: self._buffer.writerIndex))

            // Shorten the range.
            let removedBytes = subrange.count - newElements.count
            self._buffer.moveWriterIndex(to: self._buffer.writerIndex - removedBytes)
            self._range = self._range.dropLast(removedBytes)
        } else {
            // Make space for the new elements.

Review comment (bug):
This does not correctly manipulate `self._range`, and so fails to move `endIndex`. Can you add a test for this function? That test should reveal this issue.

Code after:
extension ByteBufferView: RangeReplaceableCollection {
    // required by `RangeReplaceableCollection`
    @inlinable
    public init() {
        self = ByteBufferView(ByteBuffer())
    }

    /// Reserves enough space in the underlying `ByteBuffer` such that this view can
    /// store the specified number of bytes without reallocation.
    ///
    /// See the documentation for ``ByteBuffer.reserveCapacity(_:)`` for more details.
    @inlinable
    public mutating func reserveCapacity(_ minimumCapacity: Int) {
        let additionalCapacity = minimumCapacity - self.count
        if additionalCapacity > 0 {
            self._buffer.reserveCapacity(self._buffer.capacity + additionalCapacity)
        }
    }

    /// Writes a single byte to the underlying `ByteBuffer`.
    @inlinable
    public mutating func append(_ byte: UInt8) {
        // ``CollectionOfOne`` has no witness for
        // ``Sequence.withContiguousStorageIfAvailable(_:)``. so we do this instead:
        self._buffer.setInteger(byte, at: self._range.upperBound)
        self._range = self._range.lowerBound ..< self._range.upperBound.advanced(by: 1)
        self._buffer.moveWriterIndex(to: self._range.upperBound)
    }

    /// Writes a sequence of bytes to the underlying `ByteBuffer`.
    @inlinable
    public mutating func append<Bytes: Sequence>(contentsOf bytes: Bytes) where Bytes.Element == UInt8 {
        let written = self._buffer.setBytes(bytes, at: self._range.upperBound)
        self._range = self._range.lowerBound ..< self._range.upperBound.advanced(by: written)
        self._buffer.moveWriterIndex(to: self._range.upperBound)
    }

    @inlinable
    public mutating func replaceSubrange<C: Collection>(_ subrange: Range<Index>, with newElements: C) where ByteBufferView.Element == C.Element {
        precondition(subrange.startIndex >= self.startIndex && subrange.endIndex <= self.endIndex,
                     "subrange out of bounds")

        if newElements.count == subrange.count {
            self._buffer.setBytes(newElements, at: subrange.startIndex)
        } else if newElements.count < subrange.count {
            // Replace the subrange.
            self._buffer.setBytes(newElements, at: subrange.startIndex)

            // Remove the unwanted bytes between the newly copied bytes and the end of the subrange.
            // try! is fine here: the copied range is within the view and the length can't be negative.
            try! self._buffer.copyBytes(at: subrange.endIndex,