Skip to content

Commit e41a42b

Browse files
[SwiftSyntax] Ensure SyntaxRewriter respects nil children when rewriting (#15052)
1 parent 71d3190 commit e41a42b

File tree

4 files changed

+36
-1
lines changed

4 files changed

+36
-1
lines changed

test/SwiftSyntax/Inputs/closure.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// A closure without a signature. The test will ensure it stays the same after
2+
// applying a rewriting pass.
3+
let x: () -> Void = {}

test/SwiftSyntax/VisitorTest.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,20 @@ VisitorTests.test("Basic") {
3737
})
3838
}
3939

40+
VisitorTests.test("RewritingNodeWithEmptyChild") {
41+
class ClosureRewriter: SyntaxRewriter {
42+
override func visit(_ node: ClosureExprSyntax) -> ExprSyntax {
43+
// Perform a no-op transform that requires rebuilding the node.
44+
return node.withSignature(node.signature)
45+
}
46+
}
47+
expectDoesNotThrow({
48+
let parsed = try SourceFileSyntax.decodeSourceFileSyntax(try
49+
SwiftLang.parse(getInput("closure.swift")))
50+
let rewriter = ClosureRewriter()
51+
let rewritten = rewriter.visit(parsed)
52+
expectEqual(parsed.description, rewritten.description)
53+
})
54+
}
55+
4056
runAllTests()

tools/SwiftSyntax/Syntax.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,12 @@ extension Syntax {
6868
return SyntaxChildren(node: self)
6969
}
7070

71+
/// The number of children, `present` or `missing`, in this node.
72+
/// This value can be used safely with `child(at:)`.
73+
public var numberOfChildren: Int {
74+
return data.childCaches.count
75+
}
76+
7177
/// Whether or not this node it marked as `present`.
7278
public var isPresent: Bool {
7379
return raw.presence == .present

tools/SwiftSyntax/SyntaxRewriter.swift.gyb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,17 @@ open class SyntaxRewriter {
6464
}
6565

6666
func visitChildren(_ node: Syntax) -> Syntax {
67-
let newLayout = node.children.map { visit($0).raw }
67+
// Visit all children of this node, returning `nil` if child is not
68+
// present. This will ensure that there are always the same number
69+
// of children after transforming.
70+
let newLayout = (0..<node.numberOfChildren).map { (i: Int) -> RawSyntax? in
71+
guard let child = node.child(at: i) else { return nil }
72+
return visit(child).raw
73+
}
74+
75+
// Sanity check, ensure the new children are the same length.
76+
assert(newLayout.count == node.raw.layout.count)
77+
6878
return makeSyntax(node.raw.replacingLayout(newLayout))
6979
}
7080
}

0 commit comments

Comments
 (0)