Skip to content

Commit e6b40de

Browse files
authored
Merge pull request #1706 from ahoppen/ahoppen/flip-token-presence
Add a new test mutation strategy to flip the presence of token in the `assertParse` tests (fixes 5 parser bugs)
2 parents 6d65e39 + e912ad4 commit e6b40de

18 files changed

+468
-142
lines changed

CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -549,11 +549,33 @@ public let DECL_NODES: [Node] = [
549549
kind: .editorPlaceholderDecl,
550550
base: .decl,
551551
nameForDiagnostics: "editor placeholder",
552+
documentation: """
553+
An editor placeholder, e.g. `<#declaration#>` that is used in a position that expects a declaration.
554+
""",
555+
traits: [
556+
"WithAttributes",
557+
"WithModifiers",
558+
],
552559
children: [
553560
Child(
554-
name: "Identifier",
555-
kind: .token(choices: [.token(tokenKind: "IdentifierToken")])
556-
)
561+
name: "Attributes",
562+
kind: .collection(kind: .attributeList, collectionElementName: "Attribute"),
563+
documentation: "If there were attributes before the editor placeholder, the ``EditorPlaceholderDecl`` will contain these.",
564+
isOptional: true
565+
),
566+
Child(
567+
name: "Modifiers",
568+
kind: .collection(kind: .modifierList, collectionElementName: "Modifier"),
569+
documentation: "If there were modifiers before the editor placeholder, the `EditorPlaceholderDecl` will contain these.",
570+
isOptional: true
571+
),
572+
Child(
573+
name: "Placeholder",
574+
kind: .token(choices: [.token(tokenKind: "IdentifierToken")]),
575+
documentation: """
576+
The actual editor placeholder that starts with `<#` and ends with `#>`.
577+
"""
578+
),
557579
]
558580
),
559581

Sources/SwiftParser/Declarations.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,9 @@ extension Parser {
286286
let placeholder = self.consumeAnyToken()
287287
return RawDeclSyntax(
288288
RawEditorPlaceholderDeclSyntax(
289-
identifier: placeholder,
289+
attributes: attrs.attributes,
290+
modifiers: attrs.modifiers,
291+
placeholder: placeholder,
290292
arena: self.arena
291293
)
292294
)
@@ -474,8 +476,12 @@ extension Parser {
474476
)
475477
}
476478

477-
precondition(self.currentToken.starts(with: "<"))
478-
let langle = self.consumePrefix("<", as: .leftAngle)
479+
let langle: RawTokenSyntax
480+
if self.currentToken.starts(with: "<") {
481+
langle = self.consumePrefix("<", as: .leftAngle)
482+
} else {
483+
langle = missingToken(.leftAngle)
484+
}
479485
var elements = [RawGenericParameterSyntax]()
480486
do {
481487
var keepGoing: RawTokenSyntax? = nil

Sources/SwiftParser/Nominals.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ extension Parser {
333333
}
334334

335335
mutating func parsePrimaryAssociatedTypes() -> RawPrimaryAssociatedTypeClauseSyntax {
336-
let langle = self.consumeAnyToken(remapping: .leftAngle)
336+
let langle = self.consumePrefix("<", as: .leftAngle)
337337
var associatedTypes = [RawPrimaryAssociatedTypeSyntax]()
338338
do {
339339
var keepGoing: RawTokenSyntax? = nil

Sources/SwiftParser/Types.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ extension Parser {
546546
inOut: nil,
547547
name: nil,
548548
secondName: nil,
549-
unexpectedBeforeColon,
549+
RawUnexpectedNodesSyntax(combining: misplacedSpecifiers, unexpectedBeforeColon, arena: self.arena),
550550
colon: nil,
551551
type: RawTypeSyntax(RawSimpleTypeIdentifierSyntax(name: first, genericArgumentClause: nil, arena: self.arena)),
552552
ellipsis: nil,

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.

0 commit comments

Comments
 (0)