Skip to content

Commit a53bccf

Browse files
committed
[Syntax] Perf: Optimize 'root' and 'ancestorOrSelf'
1 parent 445486b commit a53bccf

File tree

2 files changed

+73
-18
lines changed

2 files changed

+73
-18
lines changed

Sources/SwiftSyntax/Syntax.swift

+61-6
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,13 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
8181
}
8282
}
8383

84-
private var root: Syntax {
85-
switch info.info! {
86-
case .root(_): return self
87-
case .nonRoot(let info): return info.parent.root
84+
public var root: Syntax {
85+
return withUnownedSyntax(self) {
86+
var node = $0
87+
while let parent = node.parent {
88+
node = parent
89+
}
90+
return node.value
8891
}
8992
}
9093

@@ -129,7 +132,8 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
129132
}
130133

131134
/// "designated" memberwise initializer of `Syntax`.
132-
init(_ raw: RawSyntax, info: Info) {
135+
@_transparent
136+
init(_ raw: RawSyntax, info: __shared Info) {
133137
self.raw = raw
134138
self.info = info
135139
}
@@ -309,7 +313,7 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
309313
/// Create a ``Syntax`` node from a specialized syntax node.
310314
// Inline always so the optimizer can optimize this to a member access on `syntax` without having to go through
311315
// generics.
312-
@inline(__always)
316+
@_transparent
313317
public init(_ syntax: __shared some SyntaxProtocol) {
314318
self = syntax._syntaxNode
315319
}
@@ -380,6 +384,57 @@ extension Syntax {
380384
}
381385
}
382386

387+
/// Temporary non-owning Syntax.
388+
///
389+
/// This can be used for handling Syntax node without ARC traffic.
390+
struct UnownedSyntax {
391+
var raw: RawSyntax
392+
var info: Unmanaged<Syntax.Info>
393+
394+
@_transparent
395+
init(_ node: __shared Syntax) {
396+
self.raw = node.raw
397+
self.info = .passUnretained(node.info.unsafelyUnwrapped)
398+
}
399+
400+
/// Extract the Syntax value.
401+
@inline(__always)
402+
var value: Syntax {
403+
Syntax(raw, info: info.takeUnretainedValue())
404+
}
405+
406+
/// Get the parent of the Syntax value, but without retaining it.
407+
@inline(__always)
408+
var parent: UnownedSyntax? {
409+
return info._withUnsafeGuaranteedRef {
410+
switch $0.info.unsafelyUnwrapped {
411+
case .nonRoot(let info):
412+
return UnownedSyntax(info.parent)
413+
case .root(_):
414+
return nil
415+
}
416+
}
417+
}
418+
419+
/// Temporarily use the Syntax value.
420+
@inline(__always)
421+
func withValue<T>(_ body: (Syntax) -> T) -> T {
422+
info._withUnsafeGuaranteedRef {
423+
body(Syntax(self.raw, info: $0))
424+
}
425+
}
426+
}
427+
428+
/// Execute the `body` with ``UnownedSyntax`` of `node`.
429+
///
430+
/// This guarantees the life time of the `node` during the `body` is executed.
431+
@inline(__always)
432+
func withUnownedSyntax<T>(_ node: some SyntaxProtocol, _ body: (UnownedSyntax) -> T) -> T {
433+
return withExtendedLifetime(node) {
434+
body(UnownedSyntax(Syntax($0)))
435+
}
436+
}
437+
383438
/// ``SyntaxNode`` used to be a pervasive type name in SwiftSyntax that has been
384439
/// replaced by the ``Syntax`` type.
385440
@available(*, unavailable, message: "use 'Syntax' instead")

Sources/SwiftSyntax/SyntaxProtocol.swift

+12-12
Original file line numberDiff line numberDiff line change
@@ -212,11 +212,7 @@ extension SyntaxProtocol {
212212

213213
/// The root of the tree in which this node resides.
214214
public var root: Syntax {
215-
var this = _syntaxNode
216-
while let parent = this.parent {
217-
this = parent
218-
}
219-
return this
215+
return Syntax(self).root
220216
}
221217

222218
/// Whether or not this node has a parent.
@@ -241,14 +237,18 @@ extension SyntaxProtocol {
241237

242238
/// Returns this node or the first ancestor that satisfies `condition`.
243239
public func ancestorOrSelf<T>(mapping map: (Syntax) -> T?) -> T? {
244-
var walk: Syntax? = Syntax(self)
245-
while let unwrappedParent = walk {
246-
if let mapped = map(unwrappedParent) {
247-
return mapped
248-
}
249-
walk = unwrappedParent.parent
240+
return withUnownedSyntax(self) {
241+
var node = $0
242+
repeat {
243+
if let mapped = node.withValue(map) {
244+
return mapped
245+
}
246+
guard let parent = node.parent else {
247+
return nil
248+
}
249+
node = parent
250+
} while true
250251
}
251-
return nil
252252
}
253253
}
254254

0 commit comments

Comments
 (0)