Skip to content

Commit 1187890

Browse files
allevatoHarlan
authored andcommitted
[SwiftSyntax] Fix SyntaxChildren iteration (swiftlang#12319)
* [SwiftSyntax] Fix SyntaxChildren iteration SyntaxChildren.Iterator stops the first time it sees a nil child; since it traverses all indexes without considering presence, this causes it to terminate iteration early if there is a missing child in the list. * [SwiftSyntax] Add tests for SyntaxChildren
1 parent f107651 commit 1187890

File tree

3 files changed

+81
-3
lines changed

3 files changed

+81
-3
lines changed

test/SwiftSyntax/SyntaxChildren.swift

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// RUN: %target-run-simple-swift
2+
// REQUIRES: executable_test
3+
// REQUIRES: OS=macosx
4+
// REQUIRES: sdk_overlay
5+
6+
import Foundation
7+
import StdlibUnittest
8+
import SwiftSyntax
9+
10+
/// Verifies that there is a next item returned by the iterator and that it
11+
/// satisfies the given predicate.
12+
func expectNext<Iterator: IteratorProtocol>(
13+
_ iterator: inout Iterator,
14+
satisfies predicate: (Iterator.Element) throws -> Bool
15+
) rethrows {
16+
let next = iterator.next()
17+
expectNotNil(next)
18+
expectTrue(try predicate(next!))
19+
}
20+
21+
/// Verifies that the iterator is exhausted.
22+
func expectNextIsNil<Iterator: IteratorProtocol>(_ iterator: inout Iterator) {
23+
expectNil(iterator.next())
24+
}
25+
26+
var SyntaxChildrenAPI = TestSuite("SyntaxChildrenAPI")
27+
28+
SyntaxChildrenAPI.test("IterateWithAllPresent") {
29+
let returnStmt = SyntaxFactory.makeReturnStmt(
30+
returnKeyword: SyntaxFactory.makeReturnKeyword(),
31+
expression: SyntaxFactory.makeBlankExpr(),
32+
semicolon: SyntaxFactory.makeSemicolonToken())
33+
34+
var iterator = returnStmt.children.makeIterator()
35+
expectNext(&iterator) { ($0 as? TokenSyntax)?.tokenKind == .returnKeyword }
36+
expectNext(&iterator) { $0 is ExprSyntax }
37+
expectNext(&iterator) { ($0 as? TokenSyntax)?.tokenKind == .semicolon }
38+
expectNextIsNil(&iterator)
39+
}
40+
41+
SyntaxChildrenAPI.test("IterateWithSomeMissing") {
42+
let returnStmt = SyntaxFactory.makeReturnStmt(
43+
returnKeyword: SyntaxFactory.makeReturnKeyword(),
44+
expression: nil,
45+
semicolon: SyntaxFactory.makeSemicolonToken())
46+
47+
var iterator = returnStmt.children.makeIterator()
48+
expectNext(&iterator) { ($0 as? TokenSyntax)?.tokenKind == .returnKeyword }
49+
expectNext(&iterator) { ($0 as? TokenSyntax)?.tokenKind == .semicolon }
50+
expectNextIsNil(&iterator)
51+
}
52+
53+
SyntaxChildrenAPI.test("IterateWithAllMissing") {
54+
let returnStmt = SyntaxFactory.makeBlankReturnStmt()
55+
56+
var iterator = returnStmt.children.makeIterator()
57+
expectNextIsNil(&iterator)
58+
}
59+
60+
runAllTests()

tools/SwiftSyntax/Syntax.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import Foundation
1616
/// Each node has accessors for its known children, and allows efficient
1717
/// iteration over the children through its `children` property.
1818
public class Syntax: CustomStringConvertible {
19+
/// The type of sequence containing the indices of present children.
20+
internal typealias PresentChildIndicesSequence =
21+
LazyFilterSequence<CountableRange<Int>>
22+
1923
/// The root of the tree this node is currently in.
2024
internal let _root: SyntaxData
2125

@@ -102,6 +106,14 @@ public class Syntax: CustomStringConvertible {
102106
return Syntax.make(root: _root, data: _root)
103107
}
104108

109+
/// The sequence of indices that correspond to child nodes that are not
110+
/// missing.
111+
///
112+
/// This property is an implementation detail of `SyntaxChildren`.
113+
internal var presentChildIndices: PresentChildIndicesSequence {
114+
return raw.layout.indices.lazy.filter { self.raw.layout[$0].isPresent }
115+
}
116+
105117
/// Gets the child at the provided index in this node's children.
106118
/// - Parameter index: The index of the child node you're looking for.
107119
/// - Returns: A Syntax node for the provided child, or `nil` if there

tools/SwiftSyntax/SyntaxChildren.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,23 @@ import Foundation
1414

1515
public struct SyntaxChildren: Sequence {
1616
public struct Iterator: IteratorProtocol {
17-
var index: Int = 0
1817
let node: Syntax
18+
var indexIterator: Syntax.PresentChildIndicesSequence.Iterator
19+
20+
init(node: Syntax) {
21+
self.indexIterator = node.presentChildIndices.makeIterator()
22+
self.node = node
23+
}
1924

2025
public mutating func next() -> Syntax? {
21-
defer { index += 1 }
26+
guard let index = indexIterator.next() else { return nil }
2227
return node.child(at: index)
2328
}
2429
}
30+
2531
let node: Syntax
2632

2733
public func makeIterator() -> SyntaxChildren.Iterator {
28-
return Iterator(index: 0, node: node)
34+
return Iterator(node: node)
2935
}
3036
}

0 commit comments

Comments
 (0)