Skip to content

Commit 744275d

Browse files
committed
Add methods from RangeReplaceableCollection to SyntaxCollection
Especially the `+` operators make manipulation of `SyntaxCollection` a lot easier. `SyntaxCollection` doesn’t conform to `RangeReplaceableCollection` because of two reasons: - `RangeReplaceableCollection` assumes that creating a new colleciton and adding all elements from another collection to it results in the same collection. This is not true for `SyntaxCollection` because the new collection wouldn’t have a parent. This causes eg. the default implementation of `filter` to misbehave. - It can’t make the complexity guarantees that `RangeReplaceableCollection` requires, e.g. `append` is `O(tree depth)` instead of `O(1)`. For the same reason, also remove the conformance to `MutableCollection`.
1 parent e7c7f98 commit 744275d

File tree

4 files changed

+389
-99
lines changed

4 files changed

+389
-99
lines changed

Sources/SwiftSyntax/SyntaxCollection.swift

Lines changed: 176 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13-
public protocol SyntaxCollection: SyntaxProtocol, BidirectionalCollection, MutableCollection where Element: SyntaxProtocol {
13+
public protocol SyntaxCollection: SyntaxProtocol, BidirectionalCollection where Element: SyntaxProtocol, Index == SyntaxChildrenIndex {
1414
associatedtype Iterator = SyntaxCollectionIterator<Element>
1515

1616
/// The ``SyntaxKind`` of the syntax node that conforms to ``SyntaxCollection``.
@@ -33,6 +33,17 @@ extension SyntaxCollection {
3333
self.init(Syntax(data))!
3434
}
3535

36+
/// Initialize the collection from a collection of the same type.
37+
///
38+
/// - Note: This initializer is needed to improve source compatibility after
39+
/// the `+` operators have been added. Previously, they created an array
40+
/// that was converted to a ``SyntaxCollection`` and now they create a
41+
/// ``SyntaxCollection``, which makes the initializer a no-op.
42+
@available(*, deprecated, message: "Call to initializer is not necessary.")
43+
public init(_ collection: Self) {
44+
self = collection
45+
}
46+
3647
public init<Children: Sequence>(_ children: Children) where Children.Element == Element {
3748
let arena = SyntaxArena()
3849
// Extend the lifetime of children so their arenas don't get destroyed
@@ -208,6 +219,170 @@ public struct SyntaxCollectionIterator<E: SyntaxProtocol>: IteratorProtocol {
208219
}
209220
}
210221

222+
// MARK: Functions analogous to RangeReplaceableCollection
223+
224+
/// Defines functions that are similar to those in `RangeReplaceableCollection`.
225+
///
226+
/// SyntaxCollection doesn’t conform to `RangeReplaceableCollection` because of
227+
/// two reasons:
228+
/// - `RangeReplaceableCollection` assumes that creating a new colleciton and
229+
/// adding all elements from another collection to it results in the same
230+
/// collection. This is not true for `SyntaxCollection` because the new
231+
/// collection wouldn’t have a parent. This causes eg. the default
232+
/// implementation of `filter` to misbehave.
233+
/// - It can’t make the complexity guarantees that `RangeReplaceableCollection`
234+
/// requires, e.g. `append` is `O(tree depth)` instead of `O(1)`.
235+
extension SyntaxCollection {
236+
/// Replace the nodes in `subrange` by `newElements`.
237+
public mutating func replaceSubrange(_ subrange: Range<Self.Index>, with newElements: some Collection<Element>) {
238+
// We only access the raw nodes of `newElements` below.
239+
// Keep `newElements` alive so their arena doesn't get deallocated.
240+
withExtendedLifetime(newElements) {
241+
var newLayout = layoutView.formLayoutArray()
242+
let layoutRangeLowerBound = (subrange.lowerBound.data?.indexInParent).map(Int.init) ?? newLayout.endIndex
243+
let layoutRangeUpperBound = (subrange.upperBound.data?.indexInParent).map(Int.init) ?? newLayout.endIndex
244+
newLayout.replaceSubrange(layoutRangeLowerBound..<layoutRangeUpperBound, with: newElements.map { $0.raw })
245+
self = replacingLayout(newLayout)
246+
}
247+
}
248+
249+
/// Adds an element to the end of the collection.
250+
///
251+
/// - Parameter newElement: The element to append to the collection.
252+
public mutating func append(_ newElement: Element) {
253+
insert(newElement, at: endIndex)
254+
}
255+
256+
/// Adds the elements of a sequence to the end of this collection.
257+
///
258+
/// - Parameter newElements: The elements to append to the collection.
259+
public mutating func append(contentsOf newElements: some Sequence<Element>) {
260+
insert(contentsOf: Array(newElements), at: endIndex)
261+
}
262+
263+
/// Inserts a new element into the collection at the specified position.
264+
///
265+
/// - Parameter newElement: The new element to insert into the collection.
266+
/// - Parameter i: The position at which to insert the new element.
267+
/// `index` must be a valid index into the collection.
268+
public mutating func insert(_ newElement: Element, at i: Index) {
269+
replaceSubrange(i..<i, with: CollectionOfOne(newElement))
270+
}
271+
272+
/// Inserts the elements of a sequence into the collection at the specified
273+
/// position.
274+
///
275+
/// - Parameter newElements: The new elements to insert into the collection.
276+
/// - Parameter i: The position at which to insert the new elements. `index`
277+
/// must be a valid index of the collection.
278+
public mutating func insert(contentsOf newElements: some Collection<Element>, at i: Index) {
279+
replaceSubrange(i..<i, with: newElements)
280+
}
281+
282+
/// Removes and returns the element at the specified position.
283+
///
284+
/// - Parameter position: The position of the element to remove. `position`
285+
/// must be a valid index of the collection that is not equal to the
286+
/// collection's end index.
287+
/// - Returns: The removed element.
288+
@discardableResult
289+
public mutating func remove(at position: Index) -> Element {
290+
precondition(!isEmpty, "Can't remove from an empty collection")
291+
let result: Element = self[position]
292+
replaceSubrange(position..<index(after: position), with: EmptyCollection())
293+
return result
294+
}
295+
296+
/// Removes the elements in the specified subrange from the collection.
297+
///
298+
/// - Parameter bounds: The range of the collection to be removed. The
299+
/// bounds of the range must be valid indices of the collection.
300+
public mutating func removeSubrange(_ bounds: Range<Index>) {
301+
replaceSubrange(bounds, with: EmptyCollection())
302+
}
303+
304+
/// Creates a new collection by concatenating the elements of a collection and
305+
/// a sequence.
306+
///
307+
/// - Parameters:
308+
/// - lhs: A ``SyntaxCollection``
309+
/// - rhs: A collection or finite sequence.
310+
public static func + (lhs: Self, rhs: some Sequence<Element>) -> Self {
311+
var result = lhs
312+
result.append(contentsOf: rhs)
313+
return result
314+
}
315+
316+
/// Creates a new collection by concatenating the elements of a collection and
317+
/// a sequence.
318+
///
319+
/// - Parameters:
320+
/// - lhs: A ``SyntaxCollection``
321+
/// - rhs: A collection or finite sequence.
322+
///
323+
/// - Note: This overload exists to resolve when adding an array to a ``SyntaxCollection``.
324+
public static func + (lhs: Self, rhs: some RangeReplaceableCollection<Element>) -> Self {
325+
var result = lhs
326+
result.append(contentsOf: rhs)
327+
return result
328+
}
329+
330+
/// Creates a new collection by concatenating the elements of a sequence and a
331+
/// collection.
332+
///
333+
/// - Parameters:
334+
/// - lhs: A collection or finite sequence.
335+
/// - rhs: A range-replaceable collection.
336+
public static func + (lhs: some Sequence<Element>, rhs: Self) -> Self {
337+
var result = rhs
338+
result.insert(contentsOf: Array(lhs), at: result.startIndex)
339+
return result
340+
}
341+
342+
/// Creates a new collection by concatenating the elements of a sequence and a
343+
/// collection.
344+
///
345+
/// - Parameters:
346+
/// - lhs: A collection or finite sequence.
347+
/// - rhs: A range-replaceable collection.
348+
///
349+
/// - Note: This overload exists to resolve when adding an array to a ``SyntaxCollection``.
350+
public static func + (lhs: some RangeReplaceableCollection<Element>, rhs: Self) -> Self {
351+
var result = rhs
352+
result.insert(contentsOf: Array(lhs), at: result.startIndex)
353+
return result
354+
}
355+
356+
/// Appends the elements of a sequence to a range-replaceable collection.
357+
///
358+
/// - Parameters:
359+
/// - lhs: The ``SyntaxCollection`` to append to.
360+
/// - rhs: A collection or finite sequence.
361+
///
362+
/// - Note: This will result in an allocation of a copy of this node.
363+
public static func += (lhs: inout Self, rhs: some Sequence<Element>) {
364+
lhs.append(contentsOf: rhs)
365+
}
366+
367+
/// Returns a new ``SyntaxCollection`` that just contains the elements
368+
/// satisfying the given predicate.
369+
///
370+
/// - Parameter isIncluded: A closure that takes an element of the
371+
/// collection as its argument and returns a Boolean value indicating
372+
/// whether the element should be included in the returned collection.
373+
/// - Returns: A ``SyntaxCollection`` of the elements that `isIncluded` allowed.
374+
///
375+
/// - Note: This creates a new ``SyntaxCollection`` node. If the resulting node
376+
/// is not needed (e.g. because it’s only being iterated), convert the
377+
/// ``SyntaxCollection`` to an array first or use the `where` clause in
378+
/// a `for` statement.
379+
public func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> Self {
380+
var result = self
381+
result.replaceSubrange(self.startIndex..<self.endIndex, with: try Array(self).filter(isIncluded))
382+
return result
383+
}
384+
}
385+
211386
/// Conformance to `BidirectionalCollection`.
212387
extension SyntaxCollection {
213388
public func makeIterator() -> SyntaxCollectionIterator<Element> {

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -928,7 +928,7 @@ private extension AccessorBlockSyntax {
928928
var result = self
929929
switch self.accessors {
930930
case .accessors(let accessors):
931-
result.accessors = .accessors(AccessorDeclListSyntax(accessors + Array(newAccessors)))
931+
result.accessors = .accessors(accessors + Array(newAccessors))
932932
case .getter(let getter):
933933
if newAccessors.isEmpty {
934934
return self

Tests/SwiftSyntaxMacroExpansionTest/MacroSystemTests.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -340,9 +340,7 @@ public struct AddCompletionHandler: PeerMacro {
340340
]
341341
newParameterList = FunctionParameterListSyntax(newParameterListElements)
342342
} else {
343-
newParameterList = FunctionParameterListSyntax(
344-
parameterList + [completionHandlerParam]
345-
)
343+
newParameterList = parameterList + [completionHandlerParam]
346344
}
347345

348346
let callArguments: [String] = parameterList.map { param in

0 commit comments

Comments
 (0)