Skip to content

Commit fc54536

Browse files
committed
Make SyntaxCollection conform to RangeReplaceableCollection and remove add* methods on syntax nodes that have a collection as child
Conforming `SyntaxCollection` to `RangeReplaceableCollection` gives it a lot of the functions you know from `Swift.Array`. And with those methods, we no longer need the `add*` methods on syntax nodes that have a syntax collection as their child.
1 parent 8105452 commit fc54536

File tree

10 files changed

+226
-11
lines changed

10 files changed

+226
-11
lines changed

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxNodesFile.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ func syntaxNode(emitKind: SyntaxNodeKind) -> SourceFileSyntax {
203203
/// `\(child.varOrCaseName)` collection.
204204
/// - returns: A copy of the receiver with the provided `\(raw: childElt)`
205205
/// appended to its `\(child.varOrCaseName)` collection.
206+
@available(*, deprecated, message: "Use node.\(child.varOrCaseName).append(newElement) instead")
206207
public func add\(raw: childElt)(_ element: \(raw: childEltType)) -> \(node.kind.syntaxType) {
207208
var collection: RawSyntax
208209
let arena = SyntaxArena()

Sources/SwiftSyntax/SyntaxCollection.swift

Lines changed: 68 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, MutableCollection, RangeReplaceableCollection where Element: SyntaxProtocol {
1414
associatedtype Iterator = SyntaxCollectionIterator<Element>
1515

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

36+
/// Create an empty ``SyntaxCollection`` with no children.
37+
public init() {
38+
self.init([])
39+
}
40+
3641
public init<Children: Sequence>(_ children: Children) where Children.Element == Element {
3742
let arena = SyntaxArena()
3843
// Extend the lifetime of children so their arenas don't get destroyed
@@ -89,6 +94,19 @@ extension SyntaxCollection {
8994
return node.indexInParent
9095
}
9196

97+
/// Replace the nodes in `subrange` by `newElements`.
98+
public mutating func replaceSubrange(_ subrange: Range<SyntaxChildrenIndex>, with newElements: some Collection<Element>) {
99+
// We only access the raw nodes of `newElements` below.
100+
// Keep `newElements` alive so their arena doesn't get deallocated.
101+
withExtendedLifetime(newElements) {
102+
var newLayout = layoutView.formLayoutArray()
103+
let layoutRangeLowerBound = (subrange.lowerBound.data?.indexInParent).map(Int.init) ?? newLayout.endIndex
104+
let layoutRangeUpperBound = (subrange.upperBound.data?.indexInParent).map(Int.init) ?? newLayout.endIndex
105+
newLayout.replaceSubrange(layoutRangeLowerBound..<layoutRangeUpperBound, with: newElements.map { $0.raw })
106+
self = replacingLayout(newLayout)
107+
}
108+
}
109+
92110
/// Creates a new collection by appending the provided syntax element
93111
/// to the children.
94112
///
@@ -208,6 +226,55 @@ public struct SyntaxCollectionIterator<E: SyntaxProtocol>: IteratorProtocol {
208226
}
209227
}
210228

229+
// MARK: Specializations of functions from RangeReplaceableCollection
230+
231+
extension SyntaxCollection {
232+
/// Returns a new ``SyntaxCollection`` that just contains the elements
233+
/// satisfying the given predicate.
234+
///
235+
/// - Parameter isIncluded: A closure that takes an element of the
236+
/// collection as its argument and returns a Boolean value indicating
237+
/// whether the element should be included in the returned collection.
238+
/// - Returns: A ``SyntaxCollection`` of the elements that `isIncluded` allowed.
239+
public func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> Self {
240+
// This implementation is mor performant than `RangeReplacableCollection.filter`
241+
// because `RangeReplacableCollection.filter` calls `replaceSubrange` for
242+
// every included element, which is an expensive operation on
243+
// `SyntaxCollection` since all the parents need to be rewritten. It’s more
244+
// efficient to filter all the elements first and then create a final result.
245+
// Also, `RangeReplacableCollection.filter` creates an empty collection
246+
// first and appends to that collection, which means that the filtered
247+
// collection doesn’t have a parent. This implementation makes sure that
248+
// the filtered sequence has the same parent
249+
var result = self
250+
result.replaceSubrange(self.startIndex..<self.endIndex, with: try Array(self).filter(isIncluded))
251+
return result
252+
}
253+
254+
/// Removes all elements from the collection.
255+
///
256+
/// - Parameter keepCapacity: Ignored for `SyntaxCollection`
257+
public mutating func removeAll(keepingCapacity keepCapacity: Bool = false) {
258+
// The default implementation of this in `RangeReplacableCollection` creates
259+
// a node without a parent, which is not what we want.
260+
replaceSubrange(startIndex..<endIndex, with: EmptyCollection())
261+
}
262+
263+
/// Creates a new collection by concatenating the elements of a sequence and a
264+
/// collection. The parent of the new collection will be the parent of `rhs`
265+
///
266+
/// - Parameters:
267+
/// - lhs: A collection or finite sequence.
268+
/// - rhs: A range-replaceable collection.
269+
public static func + <Other: Sequence>(lhs: Other, rhs: Self) -> Self where Element == Other.Element {
270+
// The standard implementation in `RangeReplacableCollection` does not
271+
// maintain the parent of `rhs`. This implementation does.
272+
var result = rhs
273+
result.insert(contentsOf: Array(lhs), at: result.startIndex)
274+
return result
275+
}
276+
}
277+
211278
/// Conformance to `BidirectionalCollection`.
212279
extension SyntaxCollection {
213280
public func makeIterator() -> SyntaxCollectionIterator<Element> {

0 commit comments

Comments
 (0)