Skip to content

Commit 389f0f4

Browse files
committed
Introduce RetainedSyntaxArena so we don’t need to pass SyntaxArena across actor boundaries
1 parent f78d872 commit 389f0f4

File tree

8 files changed

+63
-26
lines changed

8 files changed

+63
-26
lines changed

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
5050
}
5151
5252
return withExtendedLifetime(rewritten) {
53-
return Syntax(node).replacingSelf(rewritten.raw, rawNodeArena: rewritten.raw.arena, allocationArena: SyntaxArena())
53+
return Syntax(node).replacingSelf(rewritten.raw, rawNodeArena: rewritten.raw.arenaReference.retained, allocationArena: SyntaxArena())
5454
}
5555
}
5656
"""

Sources/SwiftSyntax/Raw/RawSyntax.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,6 @@ public struct RawSyntax: Sendable {
216216
rawData.arenaReference
217217
}
218218

219-
@_spi(RawSyntax)
220-
public var arena: SyntaxArena {
221-
rawData.arenaReference.value
222-
}
223-
224219
internal var payload: RawSyntaxData.Payload {
225220
get { rawData.payload }
226221
}

Sources/SwiftSyntax/Syntax.swift

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,10 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
1919
indirect case nonRoot(NonRoot)
2020

2121
// For root node.
22-
struct Root: @unchecked Sendable {
23-
// Unchecked conformance to sendable is fine because `arena` is not
24-
// accessible. It is just used to keep the arena alive.
25-
private var arena: SyntaxArena
22+
struct Root: Sendable {
23+
private var arena: RetainedSyntaxArena
2624

27-
init(arena: SyntaxArena) {
25+
init(arena: RetainedSyntaxArena) {
2826
self.arena = arena
2927
}
3028
}
@@ -125,11 +123,16 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
125123
/// - rawNodeArena: The arena in which `raw` is allocated. It is passed to
126124
/// make sure the arena doesn’t get de-allocated before the ``Syntax``
127125
/// has a chance to retain it.
128-
static func forRoot(_ raw: RawSyntax, rawNodeArena: SyntaxArena) -> Syntax {
126+
static func forRoot(_ raw: RawSyntax, rawNodeArena: RetainedSyntaxArena) -> Syntax {
129127
precondition(rawNodeArena == raw.arenaReference)
130128
return Syntax(raw, info: .root(.init(arena: rawNodeArena)))
131129
}
132130

131+
static func forRoot(_ raw: RawSyntax, rawNodeArena: SyntaxArena) -> Syntax {
132+
precondition(rawNodeArena == raw.arenaReference)
133+
return Syntax(raw, info: .root(.init(arena: RetainedSyntaxArena(rawNodeArena))))
134+
}
135+
133136
/// Returns the child data at the provided index in this data's layout.
134137
/// - Note: This has O(n) performance, prefer using a proper Sequence type
135138
/// if applicable, instead of this.
@@ -157,7 +160,7 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
157160
/// - allocationArena: The arena in which new nodes should be allocated
158161
/// - Returns: A syntax tree with all parents where this node has been
159162
/// replaced by `newRaw`
160-
func replacingSelf(_ newRaw: RawSyntax, rawNodeArena: SyntaxArena, allocationArena: SyntaxArena) -> Syntax {
163+
func replacingSelf(_ newRaw: RawSyntax, rawNodeArena: RetainedSyntaxArena, allocationArena: SyntaxArena) -> Syntax {
161164
precondition(newRaw.arenaReference == rawNodeArena)
162165
// If we have a parent already, then ask our current parent to copy itself
163166
// recursively up to the root.
@@ -182,45 +185,50 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
182185
/// - Returns: The new root node created by this operation, and the new child
183186
/// syntax data.
184187
/// - SeeAlso: replacingSelf(_:)
185-
func replacingChild(at index: Int, with newChild: RawSyntax?, rawNodeArena: SyntaxArena?, allocationArena: SyntaxArena) -> Syntax {
188+
func replacingChild(at index: Int, with newChild: RawSyntax?, rawNodeArena: RetainedSyntaxArena?, allocationArena: SyntaxArena) -> Syntax {
186189
precondition(newChild == nil || (rawNodeArena != nil && newChild!.arenaReference == rawNodeArena!))
187190
// After newRaw has been allocated in `allocationArena`, `rawNodeArena` will
188191
// be a child arena of `allocationArena` and thus, `allocationArena` will
189192
// keep `newChild` alive.
190193
let newRaw = withExtendedLifetime(rawNodeArena) {
191194
raw.layoutView!.replacingChild(at: index, with: newChild, arena: allocationArena)
192195
}
193-
return replacingSelf(newRaw, rawNodeArena: allocationArena, allocationArena: allocationArena)
196+
return replacingSelf(newRaw, rawNodeArena: RetainedSyntaxArena(allocationArena), allocationArena: allocationArena)
197+
}
198+
199+
/// Same as `replacingChild(at:with:rawNodeArena:allocationArena:)` but takes a `__SyntaxArena` instead of a `RetainedSyntaxArena`.
200+
func replacingChild(at index: Int, with newChild: RawSyntax?, rawNodeArena: SyntaxArena?, allocationArena: SyntaxArena) -> Syntax {
201+
return self.replacingChild(at: index, with: newChild, rawNodeArena: rawNodeArena.map(RetainedSyntaxArena.init), allocationArena: allocationArena)
194202
}
195203

196204
/// Identical to `replacingChild(at: Int, with: RawSyntax?, arena: SyntaxArena)`
197205
/// that ensures that the arena of`newChild` doesn’t get de-allocated before
198206
/// `newChild` has been addded to the result.
199207
func replacingChild(at index: Int, with newChild: Syntax?, arena: SyntaxArena) -> Syntax {
200208
return withExtendedLifetime(newChild) {
201-
return replacingChild(at: index, with: newChild?.raw, rawNodeArena: newChild?.raw.arena, allocationArena: arena)
209+
return replacingChild(at: index, with: newChild?.raw, rawNodeArena: newChild?.raw.arenaReference.retained, allocationArena: arena)
202210
}
203211
}
204212

205213
func withLeadingTrivia(_ leadingTrivia: Trivia, arena: SyntaxArena) -> Syntax {
206214
if let raw = raw.withLeadingTrivia(leadingTrivia, arena: arena) {
207-
return replacingSelf(raw, rawNodeArena: arena, allocationArena: arena)
215+
return replacingSelf(raw, rawNodeArena: RetainedSyntaxArena(arena), allocationArena: arena)
208216
} else {
209217
return self
210218
}
211219
}
212220

213221
func withTrailingTrivia(_ trailingTrivia: Trivia, arena: SyntaxArena) -> Syntax {
214222
if let raw = raw.withTrailingTrivia(trailingTrivia, arena: arena) {
215-
return replacingSelf(raw, rawNodeArena: arena, allocationArena: arena)
223+
return replacingSelf(raw, rawNodeArena: RetainedSyntaxArena(arena), allocationArena: arena)
216224
} else {
217225
return self
218226
}
219227
}
220228

221229
func withPresence(_ presence: SourcePresence, arena: SyntaxArena) -> Syntax {
222230
if let raw = raw.tokenView?.withPresence(presence, arena: arena) {
223-
return replacingSelf(raw, rawNodeArena: arena, allocationArena: arena)
231+
return replacingSelf(raw, rawNodeArena: RetainedSyntaxArena(arena), allocationArena: arena)
224232
} else {
225233
return self
226234
}
@@ -234,10 +242,15 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
234242
}
235243

236244
@_spi(RawSyntax)
237-
public init(raw: RawSyntax, rawNodeArena: __shared SyntaxArena) {
245+
public init(raw: RawSyntax, rawNodeArena: __shared RetainedSyntaxArena) {
238246
self = .forRoot(raw, rawNodeArena: rawNodeArena)
239247
}
240248

249+
@_spi(RawSyntax)
250+
public init(raw: RawSyntax, rawNodeArena: __shared SyntaxArena) {
251+
self = .forRoot(raw, rawNodeArena: RetainedSyntaxArena(rawNodeArena))
252+
}
253+
241254
/// Create a ``Syntax`` node from a specialized syntax node.
242255
public init(_ syntax: some SyntaxProtocol) {
243256
self = syntax._syntaxNode

Sources/SwiftSyntax/SyntaxArena.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,22 @@ public class ParsingSyntaxArena: SyntaxArena {
234234
}
235235
}
236236

237+
/// An opaque wrapper around `SyntaxArena` that keeps the arena alive.
238+
@_spi(RawSyntax)
239+
public struct RetainedSyntaxArena: @unchecked Sendable {
240+
// Unchecked conformance to sendable is fine because `arena` is not
241+
// accessible. It is just used to keep the arena alive.
242+
private let arena: SyntaxArena
243+
244+
init(_ arena: SyntaxArena) {
245+
self.arena = arena
246+
}
247+
248+
fileprivate func arenaRef() -> SyntaxArenaRef {
249+
return SyntaxArenaRef(arena)
250+
}
251+
}
252+
237253
/// Unsafely unowned reference to ``SyntaxArena``. The user is responsible to
238254
/// maintain the lifetime of the ``SyntaxArena``.
239255
///
@@ -267,6 +283,11 @@ struct SyntaxArenaRef: Hashable, @unchecked Sendable {
267283
self._value.release()
268284
}
269285

286+
/// Get an opaque wrapper that keeps the syntax arena alive.
287+
var retained: RetainedSyntaxArena {
288+
return RetainedSyntaxArena(_value.takeUnretainedValue())
289+
}
290+
270291
func hash(into hasher: inout Hasher) {
271292
hasher.combine(_value.toOpaque())
272293
}
@@ -282,4 +303,12 @@ struct SyntaxArenaRef: Hashable, @unchecked Sendable {
282303
static func == (lhs: __shared SyntaxArena, rhs: SyntaxArenaRef) -> Bool {
283304
return rhs == lhs
284305
}
306+
307+
static func == (lhs: SyntaxArenaRef, rhs: RetainedSyntaxArena) -> Bool {
308+
return lhs == rhs.arenaRef()
309+
}
310+
311+
static func == (lhs: RetainedSyntaxArena, rhs: SyntaxArenaRef) -> Bool {
312+
return rhs == lhs
313+
}
285314
}

Sources/SwiftSyntax/SyntaxCollection.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ extension SyntaxCollection {
4949
arena: arena
5050
)
5151
}
52-
self = Syntax.forRoot(raw, rawNodeArena: arena).cast(Self.self)
52+
self = Syntax.forRoot(raw, rawNodeArena: RetainedSyntaxArena(arena)).cast(Self.self)
5353
}
5454

5555
public init(arrayLiteral elements: Element...) {
@@ -70,7 +70,7 @@ extension SyntaxCollection {
7070
internal func replacingLayout(_ layout: [RawSyntax?]) -> Self {
7171
let arena = SyntaxArena()
7272
let newRaw = layoutView.replacingLayout(with: layout, arena: arena)
73-
return Syntax(self).replacingSelf(newRaw, rawNodeArena: arena, allocationArena: arena).cast(Self.self)
73+
return Syntax(self).replacingSelf(newRaw, rawNodeArena: RetainedSyntaxArena(arena), allocationArena: arena).cast(Self.self)
7474
}
7575

7676
/// Return the index of `node` within this collection.

Sources/SwiftSyntax/SyntaxProtocol.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public extension SyntaxProtocol {
159159
// Make sure `self` (and thus the arena of `self.raw`) can’t get deallocated
160160
// before the detached node can be created.
161161
return withExtendedLifetime(self) {
162-
return Syntax(raw: self.raw, rawNodeArena: self.raw.arena).cast(Self.self)
162+
return Syntax(raw: self.raw, rawNodeArena: self.raw.arenaReference.retained).cast(Self.self)
163163
}
164164
}
165165
}

Sources/SwiftSyntax/TokenSyntax.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public struct TokenSyntax: SyntaxProtocol, SyntaxHashable {
5757
tokenDiagnostic: nil,
5858
arena: arena
5959
)
60-
self = Syntax.forRoot(raw, rawNodeArena: arena).cast(TokenSyntax.self)
60+
self = Syntax.forRoot(raw, rawNodeArena: RetainedSyntaxArena(arena)).cast(TokenSyntax.self)
6161
}
6262

6363
/// Whether the token is present or missing.
@@ -111,7 +111,7 @@ public struct TokenSyntax: SyntaxProtocol, SyntaxHashable {
111111
}
112112
let arena = SyntaxArena()
113113
let newRaw = tokenView.withKind(newValue, arena: arena)
114-
self = Syntax(self).replacingSelf(newRaw, rawNodeArena: arena, allocationArena: arena).cast(TokenSyntax.self)
114+
self = Syntax(self).replacingSelf(newRaw, rawNodeArena: RetainedSyntaxArena(arena), allocationArena: arena).cast(TokenSyntax.self)
115115
}
116116
}
117117

Sources/SwiftSyntax/generated/SyntaxRewriter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ open class SyntaxRewriter {
3636
}
3737

3838
return withExtendedLifetime(rewritten) {
39-
return Syntax(node).replacingSelf(rewritten.raw, rawNodeArena: rewritten.raw.arena, allocationArena: SyntaxArena())
39+
return Syntax(node).replacingSelf(rewritten.raw, rawNodeArena: rewritten.raw.arenaReference.retained, allocationArena: SyntaxArena())
4040
}
4141
}
4242

0 commit comments

Comments
 (0)