Skip to content

Commit e912ad4

Browse files
committed
Run ParseDiagnosticGenerator on tree with flipped token presence
This uncovered a couple of implicit assumptions, mostly around the fact that tokens insided `UnexpectedNodesSyntax` are present, which isn’t true in general for manually generated trees. It also uncovered an issue where we weren’t able to retrieve the trivia pieces of a token after it had been modified using `with` because after the modification the token was a `parsedToken` that no longer resided in a `ParsingSyntaxArena`.
1 parent 2fe34ac commit e912ad4

File tree

7 files changed

+104
-43
lines changed

7 files changed

+104
-43
lines changed

CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -560,19 +560,19 @@ public let DECL_NODES: [Node] = [
560560
Child(
561561
name: "Attributes",
562562
kind: .collection(kind: .attributeList, collectionElementName: "Attribute"),
563-
description: "If there were attributes before the editor placeholder, the ``EditorPlaceholderDecl`` will contain these.",
563+
documentation: "If there were attributes before the editor placeholder, the ``EditorPlaceholderDecl`` will contain these.",
564564
isOptional: true
565565
),
566566
Child(
567567
name: "Modifiers",
568568
kind: .collection(kind: .modifierList, collectionElementName: "Modifier"),
569-
description: "If there were modifiers before the editor placeholder, the `EditorPlaceholderDecl` will contain these.",
569+
documentation: "If there were modifiers before the editor placeholder, the `EditorPlaceholderDecl` will contain these.",
570570
isOptional: true
571571
),
572572
Child(
573573
name: "Placeholder",
574574
kind: .token(choices: [.token(tokenKind: "IdentifierToken")]),
575-
description: """
575+
documentation: """
576576
The actual editor placeholder that starts with `<#` and ends with `#>`.
577577
"""
578578
),

Sources/SwiftParserDiagnostics/MissingTokenError.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import SwiftDiagnostics
1717
extension ParseDiagnosticsGenerator {
1818
func handleMissingToken(_ missingToken: TokenSyntax) {
1919
guard let invalidToken = missingToken.previousToken(viewMode: .all),
20+
invalidToken.presence == .present,
2021
let invalidTokenContainer = invalidToken.parent?.as(UnexpectedNodesSyntax.self),
2122
invalidTokenContainer.count == 1
2223
else {

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,26 @@ import SwiftDiagnostics
1515
@_spi(RawSyntax) import SwiftSyntax
1616

1717
fileprivate func getTokens(between first: TokenSyntax, and second: TokenSyntax) -> [TokenSyntax] {
18+
var first = first
19+
if first.presence == .missing {
20+
let nextPresentToken = first.nextToken(viewMode: .sourceAccurate)
21+
guard let nextPresentToken else {
22+
return []
23+
}
24+
first = nextPresentToken
25+
}
26+
precondition(first.presence == .present)
27+
28+
var second = second
29+
if second.presence == .missing {
30+
let previousPresentToken = second.previousToken(viewMode: .sourceAccurate)
31+
guard let previousPresentToken else {
32+
return []
33+
}
34+
second = previousPresentToken
35+
}
36+
precondition(second.presence == .present)
37+
1838
var tokens: [TokenSyntax] = []
1939
var currentToken = first
2040

@@ -149,7 +169,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
149169
removeRedundantFixIt: (_ misplacedTokens: [TokenSyntax]) -> FixItMessage? = { _ in nil }
150170
) {
151171
guard let incorrectContainer = unexpected,
152-
let misplacedTokens = incorrectContainer.onlyTokens(satisfying: unexpectedTokenCondition)
172+
let misplacedTokens = incorrectContainer.onlyPresentTokens(satisfying: unexpectedTokenCondition)
153173
else {
154174
// If there are no unexpected nodes or the unexpected contain multiple tokens, don't emit a diagnostic.
155175
return
@@ -197,7 +217,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
197217
message: (TokenSyntax) -> some DiagnosticMessage
198218
) {
199219
guard let unexpected = unexpected,
200-
let misplacedToken = unexpected.onlyToken(where: predicate)
220+
let misplacedToken = unexpected.onlyPresentToken(where: predicate)
201221
else {
202222
// If there is no unexpected node or the unexpected doesn't have the
203223
// expected token, don't emit a diagnostic.
@@ -279,7 +299,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
279299
}
280300
if specifier.presence == .present {
281301
for case .some(let unexpected) in unexpectedNodes {
282-
for duplicateSpecifier in unexpected.tokens(satisfying: isOfSameKind) {
302+
for duplicateSpecifier in unexpected.presentTokens(satisfying: isOfSameKind) {
283303
addDiagnostic(
284304
duplicateSpecifier,
285305
DuplicateEffectSpecifiers(correctSpecifier: specifier, unexpectedSpecifier: duplicateSpecifier),
@@ -315,29 +335,36 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
315335
suppressRemainingDiagnostics = true
316336
return .skipChildren
317337
}
318-
if let tryKeyword = node.onlyToken(where: { $0.tokenKind == .keyword(.try) }),
338+
if let tryKeyword = node.onlyPresentToken(where: { $0.tokenKind == .keyword(.try) }),
319339
let nextToken = tryKeyword.nextToken(viewMode: .sourceAccurate),
320340
nextToken.tokenKind.isLexerClassifiedKeyword,
321341
!(node.parent?.is(TypeEffectSpecifiersSyntax.self) ?? false)
322342
{
323343
addDiagnostic(node, TryCannotBeUsed(nextToken: nextToken))
324-
} else if let semicolons = node.onlyTokens(satisfying: { $0.tokenKind == .semicolon }) {
344+
} else if let semicolons = node.onlyPresentTokens(satisfying: { $0.tokenKind == .semicolon }) {
325345
addDiagnostic(
326346
node,
327347
.unexpectedSemicolon,
328348
fixIts: [
329349
FixIt(message: RemoveNodesFixIt(semicolons), changes: semicolons.map { FixIt.MultiNodeChange.makeMissing($0) })
330350
]
331351
)
332-
} else if node.first?.as(TokenSyntax.self)?.tokenKind.isIdentifier == true,
352+
} else if let firstToken = node.first?.as(TokenSyntax.self),
353+
firstToken.tokenKind.isIdentifier == true,
354+
firstToken.presence == .present,
333355
let previousToken = node.previousToken(viewMode: .sourceAccurate),
334356
previousToken.tokenKind.isIdentifier,
335357
previousToken.parent?.is(DeclSyntax.self) == true || previousToken.parent?.is(IdentifierPatternSyntax.self) == true
336358
{
337359
// If multiple identifiers are used for a declaration name, offer to join them together.
338360
let tokens =
339361
node
340-
.prefix(while: { $0.as(TokenSyntax.self)?.tokenKind.isIdentifier == true })
362+
.prefix(while: {
363+
guard let token = $0.as(TokenSyntax.self) else {
364+
return false
365+
}
366+
return token.tokenKind.isIdentifier == true && token.presence == .present
367+
})
341368
.map({ $0.as(TokenSyntax.self)! })
342369
let joined = previousToken.text + tokens.map(\.text).joined()
343370
var fixIts: [FixIt] = [
@@ -494,7 +521,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
494521
}
495522

496523
if let unexpectedAfterRightParen = node.unexpectedAfterRightParen,
497-
let (_, falseKeyword) = unexpectedAfterRightParen.twoTokens(
524+
let (_, falseKeyword) = unexpectedAfterRightParen.twoPresentTokens(
498525
firstSatisfying: { $0.tokenKind == .binaryOperator("==") },
499526
secondSatisfying: { $0.tokenKind == .keyword(.false) }
500527
)
@@ -531,7 +558,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
531558
return .skipChildren
532559
}
533560
if let unexpected = node.unexpectedBetweenPlatformAndVersion,
534-
unexpected.onlyToken(where: { $0.tokenKind == .binaryOperator(">=") }) != nil
561+
unexpected.onlyPresentToken(where: { $0.tokenKind == .binaryOperator(">=") }) != nil
535562
{
536563
addDiagnostic(
537564
unexpected,
@@ -678,7 +705,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
678705
}
679706

680707
if let unexpected = node.unexpectedBetweenBodyAndTrailingComma,
681-
let token = unexpected.tokens(satisfying: { $0.tokenKind == .binaryOperator("&&") }).first,
708+
let token = unexpected.presentTokens(satisfying: { $0.tokenKind == .binaryOperator("&&") }).first,
682709
let trailingComma = node.trailingComma,
683710
trailingComma.presence == .missing,
684711
let previous = node.unexpectedBetweenBodyAndTrailingComma?.previousToken(viewMode: .sourceAccurate)
@@ -709,7 +736,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
709736
return .skipChildren
710737
}
711738
if let unexpected = node.unexpectedBetweenDeinitKeywordAndBody,
712-
let name = unexpected.filter({ $0.as(TokenSyntax.self)?.tokenKind.isIdentifier == true }).only?.as(TokenSyntax.self)
739+
let name = unexpected.presentTokens(satisfying: { $0.tokenKind.isIdentifier == true }).only?.as(TokenSyntax.self)
713740
{
714741
addDiagnostic(
715742
name,
@@ -741,7 +768,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
741768
return .skipChildren
742769
}
743770
if node.floatingDigits.presence == .missing,
744-
let (period, integerLiteral) = node.unexpectedAfterFloatingDigits?.twoTokens(
771+
let (period, integerLiteral) = node.unexpectedAfterFloatingDigits?.twoPresentTokens(
745772
firstSatisfying: { $0.tokenKind == .period },
746773
secondSatisfying: { $0.tokenKind.isIntegerLiteral }
747774
)
@@ -772,7 +799,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
772799
// Detect C-style for loops based on two semicolons which could not be parsed between the 'for' keyword and the '{'
773800
// This is mostly a proof-of-concept implementation to produce more complex diagnostics.
774801
if let unexpectedCondition = node.body.unexpectedBeforeLeftBrace,
775-
unexpectedCondition.tokens(withKind: .semicolon).count == 2
802+
unexpectedCondition.presentTokens(withKind: .semicolon).count == 2
776803
{
777804
// FIXME: This is aweful. We should have a way to either get all children between two cursors in a syntax node or highlight a range from one node to another.
778805
addDiagnostic(
@@ -855,7 +882,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
855882
message: { _ in .typeParameterPackEllipsis }
856883
)
857884
} else if let unexpected = node.unexpectedBetweenNameAndColon,
858-
let unexpectedEllipsis = unexpected.onlyToken(where: { $0.tokenKind == .ellipsis }),
885+
let unexpectedEllipsis = unexpected.onlyPresentToken(where: { $0.tokenKind == .ellipsis }),
859886
let each = node.each
860887
{
861888
addDiagnostic(
@@ -1105,7 +1132,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
11051132
if shouldSkip(node) {
11061133
return .skipChildren
11071134
}
1108-
if let token = node.unexpectedBetweenModuleLabelAndColon?.onlyToken(where: { $0.tokenKind.isIdentifier }),
1135+
if let token = node.unexpectedBetweenModuleLabelAndColon?.onlyPresentToken(where: { $0.tokenKind.isIdentifier }),
11091136
node.moduleLabel.presence == .missing
11101137
{
11111138
addDiagnostic(
@@ -1172,9 +1199,9 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
11721199
return
11731200
}
11741201
let message: DiagnosticMessage?
1175-
if let identifier = unexpected.onlyToken(where: { $0.tokenKind.isIdentifier }) {
1202+
if let identifier = unexpected.onlyPresentToken(where: { $0.tokenKind.isIdentifier }) {
11761203
message = IdentifierNotAllowedInOperatorName(identifier: identifier)
1177-
} else if let tokens = unexpected.onlyTokens(satisfying: { _ in true }) {
1204+
} else if let tokens = unexpected.onlyPresentTokens(satisfying: { _ in true }) {
11781205
message = TokensNotAllowedInOperatorName(tokens: tokens)
11791206
} else {
11801207
message = nil
@@ -1265,7 +1292,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
12651292
return .skipChildren
12661293
}
12671294
// recover from Objective-C style literals
1268-
if let atSign = node.unexpectedBetweenOpenDelimiterAndOpenQuote?.onlyToken(where: { $0.tokenKind == .atSign }) {
1295+
if let atSign = node.unexpectedBetweenOpenDelimiterAndOpenQuote?.onlyPresentToken(where: { $0.tokenKind == .atSign }) {
12691296
addDiagnostic(
12701297
node,
12711298
.stringLiteralAtSign,
@@ -1275,7 +1302,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
12751302
handledNodes: [atSign.id]
12761303
)
12771304
}
1278-
if let singleQuote = node.unexpectedBetweenOpenDelimiterAndOpenQuote?.onlyToken(where: { $0.tokenKind == .singleQuote }) {
1305+
if let singleQuote = node.unexpectedBetweenOpenDelimiterAndOpenQuote?.onlyPresentToken(where: { $0.tokenKind == .singleQuote }) {
12791306
let fixIt = FixIt(
12801307
message: ReplaceTokensFixIt(replaceTokens: [singleQuote], replacements: [node.openQuote]),
12811308
changes: [
@@ -1325,7 +1352,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
13251352
addDiagnostic(diagnostic, handledNodes: handledNodes)
13261353
}
13271354
if case .stringSegment(let segment) = node.segments.last {
1328-
if let invalidContent = segment.unexpectedBeforeContent?.onlyToken(where: { $0.trailingTrivia.contains(where: { $0.isBackslash }) }) {
1355+
if let invalidContent = segment.unexpectedBeforeContent?.onlyPresentToken(where: { $0.trailingTrivia.contains(where: { $0.isBackslash }) }) {
13291356
let fixIt = FixIt(
13301357
message: .removeBackslash,
13311358
changes: [
@@ -1350,7 +1377,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
13501377
return .skipChildren
13511378
}
13521379
if let unexpected = node.unexpectedBetweenSubscriptKeywordAndGenericParameterClause,
1353-
let nameTokens = unexpected.onlyTokens(satisfying: { !$0.tokenKind.isLexerClassifiedKeyword })
1380+
let nameTokens = unexpected.onlyPresentTokens(satisfying: { !$0.tokenKind.isLexerClassifiedKeyword })
13541381
{
13551382
addDiagnostic(
13561383
unexpected,
@@ -1362,7 +1389,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
13621389
)
13631390
}
13641391
if let unexpected = node.indices.unexpectedBeforeLeftParen,
1365-
let nameTokens = unexpected.onlyTokens(satisfying: { !$0.tokenKind.isLexerClassifiedKeyword })
1392+
let nameTokens = unexpected.onlyPresentTokens(satisfying: { !$0.tokenKind.isLexerClassifiedKeyword })
13661393
{
13671394
addDiagnostic(
13681395
unexpected,
@@ -1471,7 +1498,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
14711498
}
14721499

14731500
if let unexpected = node.unexpectedBeforeColon,
1474-
let leftParen = unexpected.onlyToken(where: { $0.tokenKind == .leftParen })
1501+
let leftParen = unexpected.onlyPresentToken(where: { $0.tokenKind == .leftParen })
14751502
{
14761503

14771504
var handledNodes: [SyntaxIdentifier] = [
@@ -1486,7 +1513,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
14861513

14871514
var replaceTokens = [leftParen]
14881515

1489-
if let rightParen = node.unexpectedAfterInheritedTypeCollection?.onlyToken(where: { $0.tokenKind == .rightParen }) {
1516+
if let rightParen = node.unexpectedAfterInheritedTypeCollection?.onlyPresentToken(where: { $0.tokenKind == .rightParen }) {
14901517
handledNodes += [rightParen.id]
14911518
changes += [
14921519
.makeMissing(rightParen)
@@ -1531,7 +1558,8 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
15311558
return .skipChildren
15321559
}
15331560

1534-
if let token = node.unexpectedBetweenMessageLabelAndColon?.onlyToken(where: { $0.tokenKind.isIdentifier }),
1561+
if let token = node.unexpectedBetweenMessageLabelAndColon?.onlyPresentToken(where: { $0.tokenKind.isIdentifier }),
1562+
token.presence == .present,
15351563
node.messageLabel.presence == .missing
15361564
{
15371565
addDiagnostic(
@@ -1605,7 +1633,7 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
16051633
}
16061634

16071635
let unexpectedTokens: [TokenSyntax] = [detail.unexpectedBetweenLeftParenAndDetail, detail.unexpectedBetweenDetailAndRightParen]
1608-
.compactMap { $0?.tokens(viewMode: .all) }
1636+
.compactMap { $0?.tokens(viewMode: .sourceAccurate) }
16091637
.flatMap { $0 }
16101638

16111639
// If there is no unexpected tokens it means we miss a paren or set keyword.

Sources/SwiftParserDiagnostics/SyntaxExtensions.swift

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,40 @@
1414
@_spi(Diagnostics) import SwiftParser
1515

1616
extension UnexpectedNodesSyntax {
17-
func tokens(satisfying isIncluded: (TokenSyntax) -> Bool) -> [TokenSyntax] {
17+
func presentTokens(satisfying isIncluded: (TokenSyntax) -> Bool) -> [TokenSyntax] {
1818
return self.children(viewMode: .sourceAccurate).compactMap({ $0.as(TokenSyntax.self) }).filter(isIncluded)
1919
}
2020

21-
func tokens(withKind kind: TokenKind) -> [TokenSyntax] {
22-
return self.tokens(satisfying: { $0.tokenKind == kind })
21+
func presentTokens(withKind kind: TokenKind) -> [TokenSyntax] {
22+
return self.presentTokens(satisfying: { $0.tokenKind == kind })
2323
}
2424

25-
/// If this only contains a single item, which is a token satisfying `condition`, return that token, otherwise return `nil`.
26-
func onlyToken(where condition: (TokenSyntax) -> Bool) -> TokenSyntax? {
27-
if self.count == 1, let token = self.first?.as(TokenSyntax.self), condition(token) {
25+
/// If this only contains a single item, which is a present token satisfying `condition`, return that token, otherwise return `nil`.
26+
func onlyPresentToken(where condition: (TokenSyntax) -> Bool) -> TokenSyntax? {
27+
if self.count == 1,
28+
let token = self.first?.as(TokenSyntax.self),
29+
condition(token),
30+
token.presence == .present
31+
{
2832
return token
2933
} else {
3034
return nil
3135
}
3236
}
3337

34-
/// If this only contains tokens satisfying `condition`, return an array containing those tokens, otherwise return `nil`.
35-
func onlyTokens(satisfying condition: (TokenSyntax) -> Bool) -> [TokenSyntax]? {
36-
let tokens = tokens(satisfying: condition)
38+
/// If this only contains present tokens satisfying `condition`, return an array containing those tokens, otherwise return `nil`.
39+
func onlyPresentTokens(satisfying condition: (TokenSyntax) -> Bool) -> [TokenSyntax]? {
40+
let tokens = presentTokens(satisfying: condition)
3741
if tokens.count == self.count {
3842
return tokens
3943
} else {
4044
return nil
4145
}
4246
}
4347

44-
/// If this only contains two tokens, the first satisfying `firstCondition`, and the second satisfying `secondCondition`,
48+
/// If this only contains two present tokens, the first satisfying `firstCondition`, and the second satisfying `secondCondition`,
4549
/// return these tokens as a tuple, otherwise return `nil`.
46-
func twoTokens(
50+
func twoPresentTokens(
4751
firstSatisfying firstCondition: (TokenSyntax) -> Bool,
4852
secondSatisfying secondCondition: (TokenSyntax) -> Bool
4953
) -> (first: TokenSyntax, second: TokenSyntax)? {
@@ -95,7 +99,7 @@ extension SyntaxProtocol {
9599
} else {
96100
return "braces"
97101
}
98-
} else if let token = Syntax(self).as(UnexpectedNodesSyntax.self)?.onlyTokens(satisfying: { $0.tokenKind.isLexerClassifiedKeyword })?.only {
102+
} else if let token = Syntax(self).as(UnexpectedNodesSyntax.self)?.onlyPresentTokens(satisfying: { $0.tokenKind.isLexerClassifiedKeyword })?.only {
99103
return "'\(token.text)' keyword"
100104
} else if let token = Syntax(self).as(TokenSyntax.self) {
101105
return "'\(token.text)' keyword"

Sources/SwiftSyntax/Raw/RawSyntaxTokenView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public struct RawSyntaxTokenView {
9595
public var leadingRawTriviaPieces: [RawTriviaPiece] {
9696
switch raw.rawData.payload {
9797
case .parsedToken(let dat):
98-
let arena = raw.arena as! ParsingSyntaxArena
98+
let arena = raw.arena.parsingArena!
9999
return arena.parseTrivia(source: dat.leadingTriviaText, position: .leading)
100100
case .materializedToken(let dat):
101101
return Array(dat.leadingTrivia)
@@ -108,7 +108,7 @@ public struct RawSyntaxTokenView {
108108
public var trailingRawTriviaPieces: [RawTriviaPiece] {
109109
switch raw.rawData.payload {
110110
case .parsedToken(let dat):
111-
let arena = raw.arena as! ParsingSyntaxArena
111+
let arena = raw.arena.parsingArena!
112112
return arena.parseTrivia(source: dat.trailingTriviaText, position: .trailing)
113113
case .materializedToken(let dat):
114114
return Array(dat.trailingTrivia)

0 commit comments

Comments
 (0)