Skip to content

Commit 5ceea93

Browse files
committed
Store presence of a token in an explicit field
We recently changed a token’s presence to be implicitly determined by whether it contains any source code. It turns out that this has two downsides: - We cannot synthesize missing tokens with explicitly known text, like a missing `for` identifier token for the `@_dynamicReplacable` attribute - We cannot synthesize missing tokens with trivia
1 parent dd4dc98 commit 5ceea93

File tree

8 files changed

+109
-48
lines changed

8 files changed

+109
-48
lines changed

Sources/SwiftParser/Expressions.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1004,7 +1004,12 @@ extension Parser {
10041004
textRange = wholeText.startIndex ..< wholeText.startIndex + text.count
10051005
}
10061006
return RawTokenSyntax(
1007-
kind: kind, wholeText: wholeText, textRange: textRange, arena: self.arena)
1007+
kind: kind,
1008+
wholeText: wholeText,
1009+
textRange: textRange,
1010+
presence: .present,
1011+
arena: self.arena
1012+
)
10081013
}
10091014

10101015
mutating func parseStringLiteralDelimiter(
@@ -1118,6 +1123,7 @@ extension Parser {
11181123
kind: .stringSegment,
11191124
text: SyntaxText(rebasing: text[stringLiteralSegmentStart..<slashIndex]),
11201125
leadingTriviaPieces: [], trailingTriviaPieces: [],
1126+
presence: .present,
11211127
arena: self.arena)
11221128
segments.append(RawSyntax(RawStringSegmentSyntax(content: segmentToken, arena: self.arena)))
11231129

@@ -1133,6 +1139,7 @@ extension Parser {
11331139
kind: .backslash,
11341140
text: SyntaxText(rebasing: text[slashIndex..<text.index(after: slashIndex)]),
11351141
leadingTriviaPieces: [], trailingTriviaPieces: [],
1142+
presence: .present,
11361143
arena: self.arena)
11371144

11381145
// `###`
@@ -1142,6 +1149,7 @@ extension Parser {
11421149
kind: .rawStringDelimiter,
11431150
text: SyntaxText(rebasing: text[delimiterStart..<contentStart]),
11441151
leadingTriviaPieces: [], trailingTriviaPieces: [],
1152+
presence: .present,
11451153
arena: self.arena)
11461154
} else {
11471155
delim = nil
@@ -1207,6 +1215,7 @@ extension Parser {
12071215
kind: .stringSegment,
12081216
text: SyntaxText(rebasing: segment),
12091217
leadingTriviaPieces: [], trailingTriviaPieces: [],
1218+
presence: .present,
12101219
arena: self.arena)
12111220
segments.append(RawSyntax(RawStringSegmentSyntax(content: segmentToken,
12121221
arena: self.arena)))

Sources/SwiftParser/Parser.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,12 @@ extension Parser {
180180
let tok = self.currentToken
181181
self.currentToken = self.lexemes.advance()
182182
return RawTokenSyntax(
183-
kind: tok.tokenKind, wholeText: tok.wholeText, textRange: tok.textRange,
184-
arena: arena)
183+
kind: tok.tokenKind,
184+
wholeText: tok.wholeText,
185+
textRange: tok.textRange,
186+
presence: .present,
187+
arena: arena
188+
)
185189
}
186190

187191
/// Consumes the current token and sets its kind to the given `TokenKind`,
@@ -298,7 +302,9 @@ extension Parser {
298302
kind: tokenKind,
299303
wholeText: SyntaxText(rebasing: current.wholeText[..<endIndex]),
300304
textRange: current.textRange.lowerBound ..< endIndex,
301-
arena: self.arena)
305+
presence: .present,
306+
arena: self.arena
307+
)
302308

303309
// ... or a multi-character token with the first N characters being the one
304310
// that we want to consume as a separate token.

Sources/SwiftSyntax/Raw/RawSyntax.swift

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ internal struct RawSyntaxData {
5050
/// Text in `wholeText` before `textRange.lowerBound` is leading trivia and
5151
/// after `textRange.upperBound` is trailing trivia.
5252
var textRange: Range<SyntaxText.Index>
53+
54+
var presence: SourcePresence
5355
}
5456

5557
/// Token typically created with `TokenSyntax.<someToken>`.
@@ -59,6 +61,7 @@ internal struct RawSyntaxData {
5961
var triviaPieces: RawTriviaPieceBuffer
6062
var numLeadingTrivia: UInt32
6163
var byteLength: UInt32
64+
var presence: SourcePresence
6265
}
6366

6467
/// Layout node including collections.
@@ -183,12 +186,23 @@ extension RawSyntax {
183186

184187
/// The "width" of the node.
185188
///
186-
/// Sum of text byte lengths of all descendant token nodes.
189+
/// Sum of text byte lengths of all present descendant token nodes.
187190
var byteLength: Int {
188191
switch rawData.payload {
189-
case .parsedToken(let dat): return dat.wholeText.count
190-
case .materializedToken(let dat): return Int(dat.byteLength)
191-
case .layout(let dat): return dat.byteLength
192+
case .parsedToken(let dat):
193+
if dat.presence == .present {
194+
return dat.wholeText.count
195+
} else {
196+
return 0
197+
}
198+
case .materializedToken(let dat):
199+
if dat.presence == .present {
200+
return Int(dat.byteLength)
201+
} else {
202+
return 0
203+
}
204+
case .layout(let dat):
205+
return dat.byteLength
192206
}
193207
}
194208

@@ -258,18 +272,19 @@ extension RawSyntax: TextOutputStreamable, CustomStringConvertible {
258272
public func write<Target: TextOutputStream>(to target: inout Target) {
259273
switch rawData.payload {
260274
case .parsedToken(let dat):
261-
String(syntaxText: dat.wholeText).write(to: &target)
262-
break
275+
if dat.presence == .present {
276+
String(syntaxText: dat.wholeText).write(to: &target)
277+
}
263278
case .materializedToken(let dat):
264-
for p in dat.leadingTrivia { p.write(to: &target) }
265-
String(syntaxText: dat.tokenText).write(to: &target)
266-
for p in dat.trailingTrivia { p.write(to: &target) }
267-
break
279+
if dat.presence == .present {
280+
for p in dat.leadingTrivia { p.write(to: &target) }
281+
String(syntaxText: dat.tokenText).write(to: &target)
282+
for p in dat.trailingTrivia { p.write(to: &target) }
283+
}
268284
case .layout(let dat):
269285
for case let child? in dat.layout {
270286
child.write(to: &target)
271287
}
272-
break
273288
}
274289
}
275290

@@ -364,17 +379,23 @@ extension RawSyntax {
364379
/// - kind: Token kind.
365380
/// - wholeText: Whole text of this token including trailing/leading trivia.
366381
/// - textRange: Range of the token text in `wholeText`.
382+
/// - presence: Whether the token appeared in the source code or if it was synthesized.
367383
/// - arena: SyntaxArea to the result node data resides.
368384
internal static func parsedToken(
369385
kind: RawTokenKind,
370386
wholeText: SyntaxText,
371387
textRange: Range<SyntaxText.Index>,
388+
presence: SourcePresence,
372389
arena: SyntaxArena
373390
) -> RawSyntax {
374391
assert(arena.contains(text: wholeText),
375392
"token text must be managed by the arena")
376393
let payload = RawSyntaxData.ParsedToken(
377-
tokenKind: kind, wholeText: wholeText, textRange: textRange)
394+
tokenKind: kind,
395+
wholeText: wholeText,
396+
textRange: textRange,
397+
presence: presence
398+
)
378399
return RawSyntax(arena: arena, payload: .parsedToken(payload))
379400
}
380401

@@ -385,29 +406,34 @@ extension RawSyntax {
385406
/// `makeMissingToken(arena:kind:)` instead.
386407
///
387408
/// - Parameters:
388-
/// - arena: SyntaxArea to the result node data resides.
389409
/// - kind: Token kind.
390410
/// - text: Token text.
391411
/// - triviaPieces: Raw trivia pieces including leading and trailing trivia.
392412
/// - numLeadingTrivia: Number of leading trivia pieces in `triviaPieces`.
393413
/// - byteLength: Byte length of this token including trivia.
414+
/// - presence: Whether the token appeared in the source code or if it was synthesized.
415+
/// - arena: SyntaxArea to the result node data resides.
394416
internal static func materializedToken(
395417
kind: RawTokenKind,
396418
text: SyntaxText,
397419
triviaPieces: RawTriviaPieceBuffer,
398420
numLeadingTrivia: UInt32,
399421
byteLength: UInt32,
422+
presence: SourcePresence,
400423
arena: SyntaxArena
401424
) -> RawSyntax {
402425
assert(arena.contains(text: text) || kind.defaultText?.baseAddress == text.baseAddress,
403426
"token text must be managed by the arena, or known default text for the token")
404427
assert(triviaPieces.allSatisfy({$0.storedText.map({arena.contains(text: $0)}) ?? true}),
405428
"trivia text must be managed by the arena")
406429
let payload = RawSyntaxData.MaterializedToken(
407-
tokenKind: kind, tokenText: text,
430+
tokenKind: kind,
431+
tokenText: text,
408432
triviaPieces: triviaPieces,
409433
numLeadingTrivia: numLeadingTrivia,
410-
byteLength: byteLength)
434+
byteLength: byteLength,
435+
presence: presence
436+
)
411437
return RawSyntax(arena: arena, payload: .materializedToken(payload))
412438
}
413439

@@ -418,6 +444,7 @@ extension RawSyntax {
418444
/// - text: Token text.
419445
/// - leadingTriviaPieceCount: Number of leading trivia pieces.
420446
/// - trailingTriviaPieceCount: Number of trailing trivia pieces.
447+
/// - presence: Whether the token appeared in the source code or if it was synthesized.
421448
/// - arena: SyntaxArea to the result node data resides.
422449
/// - initializingLeadingTriviaWith: A closure that initializes leading trivia pieces.
423450
/// - initializingTrailingTriviaWith: A closure that initializes trailing trivia pieces.
@@ -426,6 +453,7 @@ extension RawSyntax {
426453
text: SyntaxText,
427454
leadingTriviaPieceCount: Int,
428455
trailingTriviaPieceCount: Int,
456+
presence: SourcePresence,
429457
arena: SyntaxArena,
430458
initializingLeadingTriviaWith: (UnsafeMutableBufferPointer<RawTriviaPiece>) -> Void,
431459
initializingTrailingTriviaWith : (UnsafeMutableBufferPointer<RawTriviaPiece>) -> Void
@@ -442,7 +470,9 @@ extension RawSyntax {
442470
kind: kind, text: text, triviaPieces: RawTriviaPieceBuffer(triviaBuffer),
443471
numLeadingTrivia: numericCast(leadingTriviaPieceCount),
444472
byteLength: numericCast(byteLength),
445-
arena: arena)
473+
presence: presence,
474+
arena: arena
475+
)
446476
}
447477

448478
/// Factory method to create a materialized token node.
@@ -462,20 +492,15 @@ extension RawSyntax {
462492
) -> RawSyntax {
463493
let decomposed = kind.decomposeToRaw()
464494
let rawKind = decomposed.rawKind
465-
let text: SyntaxText
466-
switch presence {
467-
case .present:
468-
text = (decomposed.string.map({arena.intern($0)}) ??
469-
decomposed.rawKind.defaultText ??
470-
"")
471-
case .missing:
472-
text = SyntaxText()
473-
}
495+
let text = (decomposed.string.map({arena.intern($0)}) ??
496+
decomposed.rawKind.defaultText ??
497+
"")
474498

475499
return .makeMaterializedToken(
476500
kind: rawKind, text: text,
477501
leadingTriviaPieceCount: leadingTrivia.count,
478502
trailingTriviaPieceCount: trailingTrivia.count,
503+
presence: presence,
479504
arena: arena,
480505
initializingLeadingTriviaWith: { buffer in
481506
guard var ptr = buffer.baseAddress else { return }
@@ -499,8 +524,10 @@ extension RawSyntax {
499524
) -> RawSyntax {
500525
let (rawKind, _) = kind.decomposeToRaw()
501526
return .materializedToken(
502-
kind: rawKind, text: "", triviaPieces: .init(start: nil, count: 0),
527+
kind: rawKind, text: rawKind.defaultText ?? "",
528+
triviaPieces: .init(start: nil, count: 0),
503529
numLeadingTrivia: 0, byteLength: 0,
530+
presence: .missing,
504531
arena: arena)
505532
}
506533
}

Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,16 @@ public struct RawTokenSyntax: RawSyntaxNodeProtocol {
116116
kind: RawTokenKind,
117117
wholeText: SyntaxText,
118118
textRange: Range<SyntaxText.Index>,
119+
presence: SourcePresence,
119120
arena: __shared SyntaxArena
120121
) {
121122
let raw = RawSyntax.parsedToken(
122-
kind: kind, wholeText: wholeText, textRange: textRange, arena: arena)
123+
kind: kind,
124+
wholeText: wholeText,
125+
textRange: textRange,
126+
presence: presence,
127+
arena: arena
128+
)
123129
self = RawTokenSyntax(raw: raw)
124130
}
125131

@@ -130,12 +136,15 @@ public struct RawTokenSyntax: RawSyntaxNodeProtocol {
130136
text: SyntaxText,
131137
leadingTriviaPieces: [RawTriviaPiece],
132138
trailingTriviaPieces: [RawTriviaPiece],
139+
presence: SourcePresence,
133140
arena: __shared SyntaxArena
134141
) {
135142
let raw = RawSyntax.makeMaterializedToken(
136-
kind: kind, text: text,
143+
kind: kind,
144+
text: text,
137145
leadingTriviaPieceCount: leadingTriviaPieces.count,
138146
trailingTriviaPieceCount: trailingTriviaPieces.count,
147+
presence: presence,
139148
arena: arena,
140149
initializingLeadingTriviaWith: { buffer in
141150
_ = buffer.initialize(from: leadingTriviaPieces)
@@ -146,10 +155,18 @@ public struct RawTokenSyntax: RawSyntaxNodeProtocol {
146155
}
147156

148157
/// Creates a missing `TokenSyntax` with the specified kind.
158+
/// If `text` is passed, it will be used to represent the missing token's text.
159+
/// If `text` is `nil`, the `kind`'s default text will be used.
160+
/// If that is also `nil`, the token will have empty text.
149161
public init(missing kind: RawTokenKind, arena: __shared SyntaxArena) {
150162
self.init(
151-
kind: kind, text: "", leadingTriviaPieces: [], trailingTriviaPieces: [],
152-
arena: arena)
163+
kind: kind,
164+
text: kind.defaultText ?? "",
165+
leadingTriviaPieces: [],
166+
trailingTriviaPieces: [],
167+
presence: .missing,
168+
arena: arena
169+
)
153170
}
154171
}
155172

Sources/SwiftSyntax/Raw/RawSyntaxTokenView.swift

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -189,19 +189,14 @@ struct RawSyntaxTokenView {
189189
}
190190

191191
var presence: SourcePresence {
192-
if self.raw.byteLength != 0 {
193-
// The node has source text associated with it. It's present.
194-
return .present
195-
}
196-
if rawKind == .eof || rawKind == .stringSegment {
197-
// The end of file token never has source code associated with it but we
198-
// still consider it valid.
199-
// String segments can be empty if they occur in an empty string literal or in between two interpolation segments.
200-
return .present
192+
switch raw.rawData.payload {
193+
case .parsedToken(let dat):
194+
return dat.presence
195+
case .materializedToken(let dat):
196+
return dat.presence
197+
case .layout(_):
198+
preconditionFailure("'presence' is a token-only property")
201199
}
202-
203-
// If none of the above apply, the node is missing.
204-
return .missing
205200
}
206201

207202
}

Sources/SwiftSyntaxParser/RawSyntax+CNodes.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ extension RawSyntax {
6565
kind: tokenKind, text: tokenText,
6666
leadingTriviaPieceCount: leadingTriviaInfo.count,
6767
trailingTriviaPieceCount: trailingTriviaInfo.count,
68+
presence: cnode.present ? .present : .missing,
6869
arena: arena,
6970
initializingLeadingTriviaWith: {
7071
initializeRawTriviaBuffer($0, 0, leadingTriviaInfo)

Tests/SwiftParserTest/Assertions.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,8 @@ func AssertParse<Node: RawSyntaxNodeProtocol>(
208208

209209
// Applying Fix-Its
210210
if let expectedFixedSource = expectedFixedSource {
211-
let fixedSource = FixItApplier.applyFixes(in: diags, to: tree).description
212-
AssertStringsEqualWithDiff(fixedSource, expectedFixedSource, file: file, line: line)
211+
let fixedTree = FixItApplier.applyFixes(in: diags, to: tree)
212+
AssertStringsEqualWithDiff(fixedTree.description, expectedFixedSource, file: file, line: line)
213213
}
214214
}
215215
}

0 commit comments

Comments
 (0)