Skip to content

Commit 11a32cf

Browse files
committed
Model the SyntaxVisitor as a class
Since all the methods on the SyntaxVisitor were mutating, it makes more sense to model the SyntaxVisitor as a class. This also increases visitation performance.
1 parent 6184faf commit 11a32cf

File tree

2 files changed

+102
-173
lines changed

2 files changed

+102
-173
lines changed

Sources/SwiftSyntax/SyntaxRewriter.swift.gyb

Lines changed: 97 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ open class SyntaxRewriter {
106106

107107
% end
108108

109-
final func visit(_ data: SyntaxData) -> Syntax {
109+
private func visit(_ data: SyntaxData) -> Syntax {
110110
switch data.raw.kind {
111111
case .token:
112112
let node = TokenSyntax(data)
@@ -133,8 +133,9 @@ open class SyntaxRewriter {
133133
}
134134
}
135135

136-
final func visitChildren<SyntaxType: SyntaxProtocol>(_ node: SyntaxType)
137-
-> SyntaxType {
136+
private func visitChildren<SyntaxType: SyntaxProtocol>(
137+
_ node: SyntaxType
138+
) -> SyntaxType {
138139
// Walk over all children of this node and rewrite them. Don't store any
139140
// rewritten nodes until the first non-`nil` value is encountered. When this
140141
// happens, retrieve all previous syntax nodes from the parent node to
@@ -222,220 +223,148 @@ public enum SyntaxVisitorContinueKind {
222223
case skipChildren
223224
}
224225

225-
public protocol SyntaxVisitor {
226+
open class SyntaxVisitor {
227+
public init() {}
228+
229+
/// Walk all nodes of the given syntax tree, calling the corresponding `visit`
230+
/// function for every node that is being visited.
231+
public func walk(_ node: Syntax) {
232+
visit(node.data)
233+
}
234+
226235
% for node in SYNTAX_NODES:
227236
% if is_visitable(node):
228237
/// Visiting `${node.name}` specifically.
229238
/// - Parameter node: the node we are visiting.
230239
/// - Returns: how should we continue visiting.
231-
mutating func visit(_ node: ${node.name}) -> SyntaxVisitorContinueKind
240+
open func visit(_ node: ${node.name}) -> SyntaxVisitorContinueKind {
241+
return .visitChildren
242+
}
232243

233244
/// The function called after visiting `${node.name}` and its descendents.
234245
/// - node: the node we just finished visiting.
235-
mutating func visitPost(_ node: ${node.name})
246+
open func visitPost(_ node: ${node.name}) {}
236247
% end
237248
% end
238249

239250
/// Visiting `TokenSyntax` specifically.
240251
/// - Parameter node: the node we are visiting.
241252
/// - Returns: how should we continue visiting.
242-
mutating func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind
253+
open func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind {
254+
return .visitChildren
255+
}
243256

244257
/// The function called after visiting the node and its descendents.
245258
/// - node: the node we just finished visiting.
246-
mutating func visitPost(_ node: TokenSyntax)
259+
open func visitPost(_ node: TokenSyntax) {}
247260

248261
/// Visiting `UnknownSyntax` specifically.
249262
/// - Parameter node: the node we are visiting.
250263
/// - Returns: how should we continue visiting.
251-
mutating func visit(_ node: UnknownSyntax) -> SyntaxVisitorContinueKind
264+
open func visit(_ node: UnknownSyntax) -> SyntaxVisitorContinueKind {
265+
return .visitChildren
266+
}
252267

253268
/// The function called after visiting the node and its descendents.
254269
/// - node: the node we just finished visiting.
255-
mutating func visitPost(_ node: UnknownSyntax)
256-
}
270+
open func visitPost(_ node: UnknownSyntax) {}
257271

258-
public extension SyntaxVisitor {
259272
% for node in SYNTAX_NODES:
260-
% if is_visitable(node):
261-
mutating func visit(_ node: ${node.name}) -> SyntaxVisitorContinueKind {
262-
return .visitChildren
263-
}
264-
mutating func visitPost(_ node: ${node.name}) {}
273+
/// Implementation detail of doVisit(_:_:). Do not call directly.
274+
private func visitImpl${node.name}(_ data: SyntaxData) {
275+
% if node.is_base():
276+
let node = Unknown${node.name}(data)
277+
let needsChildren = (visit(node) == .visitChildren)
278+
// Avoid calling into visitChildren if possible.
279+
if needsChildren && node.raw.numberOfChildren > 0 {
280+
visitChildren(node)
281+
}
282+
visitPost(node)
283+
% else:
284+
let node = ${node.name}(data)
285+
let needsChildren = (visit(node) == .visitChildren)
286+
// Avoid calling into visitChildren if possible.
287+
if needsChildren && node.raw.numberOfChildren > 0 {
288+
visitChildren(node)
289+
}
290+
visitPost(node)
265291
% end
266-
% end
267-
268-
mutating func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind {
269-
return .visitChildren
270292
}
271-
mutating func visitPost(_ node: TokenSyntax) {}
272-
273-
mutating func visit(_ node: UnknownSyntax) -> SyntaxVisitorContinueKind {
274-
return .visitChildren
275-
}
276-
mutating func visitPost(_ node: UnknownSyntax) {}
277-
}
278293

279-
/// A `SyntaxVisitor` that can visit the nodes as generic `Syntax` values.
280-
///
281-
/// This is a separate protocol because this kind of visitation is slower than
282-
/// the type-specific visitation of `SyntaxVisitor`. Use `SyntaxAnyVisitor` if
283-
/// the `visitAny(_)` function would be useful to have, otherwise use
284-
/// `SyntaxVisitor`.
285-
///
286-
/// This works by introducing default implementations of the type-specific
287-
/// visit function that delegate to `visitAny(_)`. A conformant type that
288-
/// provides a custom type-specific visit function, should also call
289-
/// `visitAny(_)` in its implementation, if calling `visitAny` is needed:
290-
///
291-
/// struct MyVisitor: SyntaxAnyVisitor {
292-
/// func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
293-
/// <code>
294-
/// }
295-
///
296-
/// func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind {
297-
/// <code>
298-
/// // Call this to pass tokens to `visitAny(_)` as well if needed
299-
/// visitAny(token)
300-
/// }
301-
///
302-
public protocol SyntaxAnyVisitor: SyntaxVisitor {
303-
mutating func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind
304-
mutating func visitAnyPost(_ node: Syntax)
305-
}
306-
307-
public extension SyntaxAnyVisitor {
308-
mutating func visitAnyPost(_ node: Syntax) {}
309-
310-
% for node in SYNTAX_NODES:
311-
% if is_visitable(node):
312-
mutating func visit(_ node: ${node.name}) -> SyntaxVisitorContinueKind {
313-
return visitAny(Syntax(node))
314-
}
315-
mutating func visitPost(_ node: ${node.name}) {
316-
return visitAnyPost(Syntax(node))
317-
}
318-
% end
319294
% end
320295

321-
mutating func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind {
322-
return visitAny(Syntax(token))
323-
}
324-
mutating func visitPost(_ node: TokenSyntax) {
325-
return visitAnyPost(Syntax(node))
296+
private func visit(_ data: SyntaxData) {
297+
switch data.raw.kind {
298+
case .token:
299+
let node = TokenSyntax(data)
300+
_ = visit(node)
301+
// No children to visit.
302+
visitPost(node)
303+
case .unknown:
304+
let node = UnknownSyntax(data)
305+
let needsChildren = (visit(node) == .visitChildren)
306+
// Avoid calling into visitChildren if possible.
307+
if needsChildren && node.raw.numberOfChildren > 0 {
308+
visitChildren(node)
309+
}
310+
visitPost(node)
311+
// The implementation of every generated case goes into its own function. This
312+
// circumvents an issue where the compiler allocates stack space for every
313+
// case statement next to each other in debug builds, causing it to allocate
314+
// ~50KB per call to this function. rdar://55929175
315+
% for node in SYNTAX_NODES:
316+
case .${node.swift_syntax_kind}:
317+
visitImpl${node.name}(data)
318+
% end
319+
}
326320
}
327321

328-
mutating func visit(_ node: UnknownSyntax) -> SyntaxVisitorContinueKind {
329-
return visitAny(Syntax(node))
330-
}
331-
mutating func visitPost(_ node: UnknownSyntax) {
332-
return visitAnyPost(Syntax(node))
322+
private func visitChildren<SyntaxType: SyntaxProtocol>(_ node: SyntaxType) {
323+
let syntaxNode = Syntax(node)
324+
let parentBox = SyntaxBox(syntaxNode)
325+
for childRaw in PresentRawSyntaxChildren(syntaxNode) {
326+
let childData = SyntaxData(childRaw, parentBox: parentBox)
327+
visit(childData)
328+
}
333329
}
334330
}
335331

336-
/// A class version of the `SyntaxVisitor` protocol. This is useful if you
337-
/// intend to have subclasses overriding specific methods of a common base
338-
/// `SyntaxVisitor` class.
339-
///
340-
/// It workarounds the issue of not being able to override the default
341-
/// implementations of the protocol (see https://bugs.swift.org/browse/SR-103).
342-
open class SyntaxVisitorBase: SyntaxVisitor {
332+
open class SyntaxAnyVisitor {
343333
public init() {}
344334

345-
% for node in SYNTAX_NODES:
346-
% if is_visitable(node):
347-
open func visit(_ node: ${node.name}) -> SyntaxVisitorContinueKind {
348-
return .visitChildren
349-
}
350-
open func visitPost(_ node: ${node.name}) {}
351-
% end
352-
% end
353-
354-
open func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind {
355-
return .visitChildren
335+
/// Walk all nodes of the given syntax tree, calling `visit` for every node
336+
/// that is being visited.
337+
public func walk(_ node: Syntax) {
338+
visit(node.data)
356339
}
357-
open func visitPost(_ node: TokenSyntax) {}
358340

359-
open func visit(_ node: UnknownSyntax) -> SyntaxVisitorContinueKind {
341+
/// Visiting `UnknownSyntax` specifically.
342+
/// - Parameter node: the node we are visiting.
343+
/// - Returns: how should we continue visiting.
344+
open func visit(_ node: Syntax) -> SyntaxVisitorContinueKind {
360345
return .visitChildren
361346
}
362-
open func visitPost(_ node: UnknownSyntax) {}
363-
}
364347

365-
public extension Syntax {
366-
func walk<Visitor>(_ visitor: inout Visitor) where Visitor : SyntaxVisitor {
367-
guard isPresent else { return }
368-
return doVisit(data, &visitor)
369-
}
370-
}
371-
372-
public extension SyntaxProtocol {
373-
func walk<Visitor>(_ visitor: inout Visitor) where Visitor : SyntaxVisitor {
374-
return _syntaxNode.walk(&visitor)
375-
}
376-
}
348+
/// The function called after visiting the node and its descendents.
349+
/// - node: the node we just finished visiting.
350+
open func visitPost(_ node: Syntax) {}
377351

378-
% for node in SYNTAX_NODES:
379-
/// Implementation detail of doVisit(_:_:). Do not call directly.
380-
private func _doVisitImpl${node.name}<Visitor>(
381-
_ data: SyntaxData, _ visitor: inout Visitor
382-
) where Visitor : SyntaxVisitor {
383-
% if node.is_base():
384-
let node = Unknown${node.name}(data)
385-
let needsChildren = (visitor.visit(node) == .visitChildren)
352+
private func visit(_ data: SyntaxData) {
353+
let node = Syntax(data)
354+
let needsChildren = (visit(node) == .visitChildren)
386355
// Avoid calling into visitChildren if possible.
387356
if needsChildren && node.raw.numberOfChildren > 0 {
388-
visitChildren(node, &visitor)
357+
visitChildren(node)
389358
}
390-
visitor.visitPost(node)
391-
% else:
392-
let node = ${node.name}(data)
393-
let needsChildren = (visitor.visit(node) == .visitChildren)
394-
// Avoid calling into visitChildren if possible.
395-
if needsChildren && node.raw.numberOfChildren > 0 {
396-
visitChildren(node, &visitor)
397-
}
398-
visitor.visitPost(node)
399-
% end
400-
}
401-
402-
% end
359+
visitPost(node)
360+
}
403361

404-
fileprivate func doVisit<Visitor>(
405-
_ data: SyntaxData, _ visitor: inout Visitor
406-
) where Visitor : SyntaxVisitor {
407-
switch data.raw.kind {
408-
case .token:
409-
let node = TokenSyntax(data)
410-
_ = visitor.visit(node)
411-
// No children to visit.
412-
visitor.visitPost(node)
413-
case .unknown:
414-
let node = UnknownSyntax(data)
415-
let needsChildren = (visitor.visit(node) == .visitChildren)
416-
// Avoid calling into visitChildren if possible.
417-
if needsChildren && node.raw.numberOfChildren > 0 {
418-
visitChildren(node, &visitor)
362+
private func visitChildren(_ node: Syntax) {
363+
let parentBox = SyntaxBox(node)
364+
for childRaw in PresentRawSyntaxChildren(node) {
365+
let childData = SyntaxData(childRaw, parentBox: parentBox)
366+
visit(childData)
419367
}
420-
visitor.visitPost(node)
421-
// The implementation of every generated case goes into its own function. This
422-
// circumvents an issue where the compiler allocates stack space for every
423-
// case statement next to each other in debug builds, causing it to allocate
424-
// ~50KB per call to this function. rdar://55929175
425-
% for node in SYNTAX_NODES:
426-
case .${node.swift_syntax_kind}:
427-
_doVisitImpl${node.name}(data, &visitor)
428-
% end
429368
}
430-
}
431369

432-
fileprivate func visitChildren<SyntaxType: SyntaxProtocol, Visitor>(
433-
_ syntax: SyntaxType, _ visitor: inout Visitor
434-
) where Visitor : SyntaxVisitor {
435-
let syntaxNode = Syntax(syntax)
436-
let parentBox = SyntaxBox(syntaxNode)
437-
for childRaw in PresentRawSyntaxChildren(syntaxNode) {
438-
let childData = SyntaxData(childRaw, parentBox: parentBox)
439-
doVisit(childData, &visitor)
440-
}
441370
}

Sources/SwiftSyntax/SyntaxVerifier.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,26 @@ public enum SyntaxVerifierError: Error, CustomStringConvertible {
2626
}
2727

2828
/// Verifier to check that there are no unknown syntax nodes in the tree.
29-
public struct SyntaxVerifier: SyntaxAnyVisitor {
29+
public class SyntaxVerifier: SyntaxAnyVisitor {
3030

3131
var unknownNodes: [Syntax] = []
3232

33-
public mutating func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind {
33+
public override func visit(_ node: Syntax) -> SyntaxVisitorContinueKind {
3434
if node.isUnknown {
3535
unknownNodes.append(node)
3636
}
3737
return .visitChildren
3838
}
3939

40-
private mutating func verify(_ node: Syntax) throws {
41-
node.walk(&self)
40+
private func verify(_ node: Syntax) throws {
41+
self.walk(node)
4242
if let unknownNode = unknownNodes.first {
4343
throw SyntaxVerifierError.unknownSyntaxFound(node: unknownNode)
4444
}
4545
}
4646

4747
public static func verify(_ node: Syntax) throws {
48-
var verifier = SyntaxVerifier()
48+
let verifier = SyntaxVerifier()
4949
try verifier.verify(node)
5050
}
5151
}

0 commit comments

Comments
 (0)