Skip to content

Commit 9ee9449

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 9ee9449

File tree

2 files changed

+98
-173
lines changed

2 files changed

+98
-173
lines changed

Sources/SwiftSyntax/SyntaxRewriter.swift.gyb

Lines changed: 93 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+
fileprivate final 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,144 @@ public enum SyntaxVisitorContinueKind {
222223
case skipChildren
223224
}
224225

225-
public protocol SyntaxVisitor {
226+
open class SyntaxVisitor {
227+
public init() {}
228+
226229
% for node in SYNTAX_NODES:
227230
% if is_visitable(node):
228231
/// Visiting `${node.name}` specifically.
229232
/// - Parameter node: the node we are visiting.
230233
/// - Returns: how should we continue visiting.
231-
mutating func visit(_ node: ${node.name}) -> SyntaxVisitorContinueKind
234+
open func visit(_ node: ${node.name}) -> SyntaxVisitorContinueKind {
235+
return .visitChildren
236+
}
232237

233238
/// The function called after visiting `${node.name}` and its descendents.
234239
/// - node: the node we just finished visiting.
235-
mutating func visitPost(_ node: ${node.name})
240+
open func visitPost(_ node: ${node.name}) {}
236241
% end
237242
% end
238243

239244
/// Visiting `TokenSyntax` specifically.
240245
/// - Parameter node: the node we are visiting.
241246
/// - Returns: how should we continue visiting.
242-
mutating func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind
247+
open func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind {
248+
return .visitChildren
249+
}
243250

244251
/// The function called after visiting the node and its descendents.
245252
/// - node: the node we just finished visiting.
246-
mutating func visitPost(_ node: TokenSyntax)
253+
open func visitPost(_ node: TokenSyntax) {}
247254

248255
/// Visiting `UnknownSyntax` specifically.
249256
/// - Parameter node: the node we are visiting.
250257
/// - Returns: how should we continue visiting.
251-
mutating func visit(_ node: UnknownSyntax) -> SyntaxVisitorContinueKind
252-
253-
/// The function called after visiting the node and its descendents.
254-
/// - node: the node we just finished visiting.
255-
mutating func visitPost(_ node: UnknownSyntax)
256-
}
257-
258-
public extension SyntaxVisitor {
259-
% for node in SYNTAX_NODES:
260-
% if is_visitable(node):
261-
mutating func visit(_ node: ${node.name}) -> SyntaxVisitorContinueKind {
258+
open func visit(_ node: UnknownSyntax) -> SyntaxVisitorContinueKind {
262259
return .visitChildren
263260
}
264-
mutating func visitPost(_ node: ${node.name}) {}
265-
% end
266-
% end
267261

268-
mutating func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind {
269-
return .visitChildren
270-
}
271-
mutating func visitPost(_ node: TokenSyntax) {}
262+
/// The function called after visiting the node and its descendents.
263+
/// - node: the node we just finished visiting.
264+
open func visitPost(_ node: UnknownSyntax) {}
272265

273-
mutating func visit(_ node: UnknownSyntax) -> SyntaxVisitorContinueKind {
274-
return .visitChildren
266+
public func walk(_ node: Syntax) {
267+
visit(node.data)
275268
}
276-
mutating func visitPost(_ node: UnknownSyntax) {}
277-
}
278-
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) {}
309269

310270
% 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-
}
271+
/// Implementation detail of doVisit(_:_:). Do not call directly.
272+
private func visitImpl${node.name}(_ data: SyntaxData) {
273+
% if node.is_base():
274+
let node = Unknown${node.name}(data)
275+
let needsChildren = (visit(node) == .visitChildren)
276+
// Avoid calling into visitChildren if possible.
277+
if needsChildren && node.raw.numberOfChildren > 0 {
278+
visitChildren(node)
279+
}
280+
visitPost(node)
281+
% else:
282+
let node = ${node.name}(data)
283+
let needsChildren = (visit(node) == .visitChildren)
284+
// Avoid calling into visitChildren if possible.
285+
if needsChildren && node.raw.numberOfChildren > 0 {
286+
visitChildren(node)
287+
}
288+
visitPost(node)
318289
% end
290+
}
291+
319292
% end
320293

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

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

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 {
330+
open class SyntaxAnyVisitor {
343331
public init() {}
344332

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 {
333+
/// Visiting `UnknownSyntax` specifically.
334+
/// - Parameter node: the node we are visiting.
335+
/// - Returns: how should we continue visiting.
336+
open func visit(_ node: Syntax) -> SyntaxVisitorContinueKind {
355337
return .visitChildren
356338
}
357-
open func visitPost(_ node: TokenSyntax) {}
358339

359-
open func visit(_ node: UnknownSyntax) -> SyntaxVisitorContinueKind {
360-
return .visitChildren
361-
}
362-
open func visitPost(_ node: UnknownSyntax) {}
363-
}
340+
/// The function called after visiting the node and its descendents.
341+
/// - node: the node we just finished visiting.
342+
open func visitPost(_ node: Syntax) {}
364343

365-
public extension Syntax {
366-
func walk<Visitor>(_ visitor: inout Visitor) where Visitor : SyntaxVisitor {
367-
guard isPresent else { return }
368-
return doVisit(data, &visitor)
344+
public func walk(_ node: Syntax) {
345+
visit(node.data)
369346
}
370-
}
371347

372-
public extension SyntaxProtocol {
373-
func walk<Visitor>(_ visitor: inout Visitor) where Visitor : SyntaxVisitor {
374-
return _syntaxNode.walk(&visitor)
375-
}
376-
}
377-
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)
348+
private func visit(_ data: SyntaxData) {
349+
let node = Syntax(data)
350+
let needsChildren = (visit(node) == .visitChildren)
386351
// Avoid calling into visitChildren if possible.
387352
if needsChildren && node.raw.numberOfChildren > 0 {
388-
visitChildren(node, &visitor)
353+
visitChildren(node)
389354
}
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
355+
visitPost(node)
356+
}
403357

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)
358+
private func visitChildren(_ node: Syntax) {
359+
let parentBox = SyntaxBox(node)
360+
for childRaw in PresentRawSyntaxChildren(node) {
361+
let childData = SyntaxData(childRaw, parentBox: parentBox)
362+
visit(childData)
419363
}
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
429364
}
430-
}
431365

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-
}
441366
}

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)