Skip to content

Commit 02da1a1

Browse files
authored
Merge pull request #1341 from ahoppen/ahoppen/fixes-found-by-source-alteration
Fix bugs found by mutating source in SwiftParserTest using alternative token choices
2 parents cf413a6 + 88b7ba6 commit 02da1a1

15 files changed

+129
-83
lines changed

Sources/SwiftParser/Attributes.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ extension Parser {
212212
mutating func parseAttribute() -> RawAttributeListSyntax.Element {
213213
if self.at(.poundIfKeyword) {
214214
return .ifConfigDecl(
215-
self.parsePoundIfDirective { parser -> RawAttributeListSyntax.Element in
215+
self.parsePoundIfDirective { (parser, _) -> RawAttributeListSyntax.Element in
216216
return parser.parseAttribute()
217217
} syntax: { parser, attributes in
218218
return .attributes(RawAttributeListSyntax(elements: attributes, arena: parser.arena))
@@ -607,7 +607,7 @@ extension Parser {
607607
mutating func parseObjectiveCSelector() -> RawObjCSelectorSyntax {
608608
var elements = [RawObjCSelectorPieceSyntax]()
609609
var loopProgress = LoopProgressCondition()
610-
while !self.at(.eof, .rightParen) && loopProgress.evaluate(currentToken) {
610+
while loopProgress.evaluate(currentToken) {
611611
// Empty selector piece.
612612
if let colon = self.consume(if: .colon) {
613613
elements.append(
@@ -618,10 +618,8 @@ extension Parser {
618618
)
619619
)
620620
continue
621-
}
622-
623-
if self.at(.identifier) || self.currentToken.isLexerClassifiedKeyword {
624-
let name = self.consumeAnyToken()
621+
} else if self.at(.identifier, .wildcard) || self.currentToken.isLexerClassifiedKeyword {
622+
let name = self.consumeAnyToken(remapping: .identifier)
625623

626624
// If we hit a ')' we may have a zero-argument selector.
627625
if self.at(.rightParen) && elements.isEmpty {
@@ -644,6 +642,8 @@ extension Parser {
644642
arena: self.arena
645643
)
646644
)
645+
} else {
646+
break
647647
}
648648
}
649649
return RawObjCSelectorSyntax(elements: elements, arena: self.arena)

Sources/SwiftParser/Declarations.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ extension Parser {
176176
// parsed when we're parsing the attributes.
177177
break
178178
}
179-
let directive = self.parsePoundIfDirective { parser in
179+
let directive = self.parsePoundIfDirective { (parser, _) in
180180
let parsedDecl = parser.parseDeclaration()
181181
let semicolon = parser.consume(if: .semicolon)
182182
return RawMemberDeclListItemSyntax(
@@ -1581,6 +1581,7 @@ extension Parser {
15811581
var attributes: RawAttributeListSyntax?
15821582
var modifier: RawDeclModifierSyntax?
15831583
var kind: AccessorKind
1584+
var unexpectedBeforeToken: RawUnexpectedNodesSyntax?
15841585
var token: RawTokenSyntax
15851586
}
15861587

@@ -1591,7 +1592,7 @@ extension Parser {
15911592
var look = self.lookahead()
15921593
let _ = look.consumeAttributeList()
15931594
let hasModifier = look.consume(if: .keyword(.mutating), .keyword(.nonmutating), .keyword(.__consuming)) != nil
1594-
guard let (kind, handle) = look.at(anyIn: AccessorKind.self) ?? forcedKind else {
1595+
guard let (kind, _) = look.at(anyIn: AccessorKind.self) ?? forcedKind else {
15951596
return nil
15961597
}
15971598

@@ -1612,11 +1613,12 @@ extension Parser {
16121613
modifier = nil
16131614
}
16141615

1615-
let introducer = self.eat(handle)
1616+
let (unexpectedBeforeIntroducer, introducer) = self.expect(kind.spec)
16161617
return AccessorIntroducer(
16171618
attributes: attrs,
16181619
modifier: modifier,
16191620
kind: kind,
1621+
unexpectedBeforeToken: unexpectedBeforeIntroducer,
16201622
token: introducer
16211623
)
16221624
}
@@ -1673,6 +1675,7 @@ extension Parser {
16731675
return RawAccessorDeclSyntax(
16741676
attributes: introducer.attributes,
16751677
modifier: introducer.modifier,
1678+
introducer.unexpectedBeforeToken,
16761679
accessorKind: introducer.token,
16771680
parameter: parameter,
16781681
effectSpecifiers: effectSpecifiers,

Sources/SwiftParser/Directives.swift

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,19 @@
1313
@_spi(RawSyntax) import SwiftSyntax
1414

1515
extension Parser {
16-
private enum IfConfigClauseStartKeyword: TokenSpecSet {
17-
case poundIfKeyword
16+
private enum IfConfigContinuationClauseStartKeyword: TokenSpecSet {
1817
case poundElseifKeyword
1918
case poundElseKeyword
2019

2120
var spec: TokenSpec {
2221
switch self {
23-
case .poundIfKeyword: return .poundIfKeyword
2422
case .poundElseifKeyword: return .poundElseifKeyword
2523
case .poundElseKeyword: return .poundElseKeyword
2624
}
2725
}
2826

2927
init?(lexeme: Lexer.Lexeme) {
3028
switch PrepareForKeywordMatch(lexeme) {
31-
case TokenSpec(.poundIfKeyword): self = .poundIfKeyword
3229
case TokenSpec(.poundElseifKeyword): self = .poundElseifKeyword
3330
case TokenSpec(.poundElseKeyword): self = .poundElseKeyword
3431
default: return nil
@@ -89,7 +86,7 @@ extension Parser {
8986
/// into a syntax collection.
9087
@_spi(RawSyntax)
9188
public mutating func parsePoundIfDirective<Element: RawSyntaxNodeProtocol>(
92-
_ parseElement: (inout Parser) -> Element?,
89+
_ parseElement: (_ parser: inout Parser, _ isFirstElement: Bool) -> Element?,
9390
addSemicolonIfNeeded: (_ lastElement: Element, _ newItemAtStartOfLine: Bool, _ parser: inout Parser) -> Element? = { _, _, _ in nil },
9491
syntax: (inout Parser, [Element]) -> RawIfConfigClauseSyntax.Elements?
9592
) -> RawIfConfigDeclSyntax {
@@ -106,7 +103,7 @@ extension Parser {
106103
do {
107104
var firstIteration = true
108105
var loopProgress = LoopProgressCondition()
109-
while let poundIfHandle = firstIteration ? self.canRecoverTo(.poundIfKeyword) : self.canRecoverTo(anyIn: IfConfigClauseStartKeyword.self)?.handle,
106+
while let poundIfHandle = firstIteration ? self.canRecoverTo(.poundIfKeyword) : self.canRecoverTo(anyIn: IfConfigContinuationClauseStartKeyword.self)?.handle,
110107
loopProgress.evaluate(self.currentToken)
111108
{
112109
let (unexpectedBeforePoundIf, poundIf) = self.eat(poundIfHandle)
@@ -127,7 +124,7 @@ extension Parser {
127124
var elementsProgress = LoopProgressCondition()
128125
while !self.at(.eof) && !self.at(.poundElseKeyword, .poundElseifKeyword, .poundEndifKeyword) && elementsProgress.evaluate(currentToken) {
129126
let newItemAtStartOfLine = self.currentToken.isAtStartOfLine
130-
guard let element = parseElement(&self), !element.isEmpty else {
127+
guard let element = parseElement(&self, elements.isEmpty), !element.isEmpty else {
131128
break
132129
}
133130
if let lastElement = elements.last, let fixedUpLastItem = addSemicolonIfNeeded(lastElement, newItemAtStartOfLine, &self) {

Sources/SwiftParser/Expressions.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,10 @@ extension Parser {
659659
) -> RawExprSyntax {
660660
assert(self.at(.poundIfKeyword))
661661

662-
let config = self.parsePoundIfDirective { parser -> RawExprSyntax? in
662+
let config = self.parsePoundIfDirective { (parser, isFirstElement) -> RawExprSyntax? in
663+
if !isFirstElement {
664+
return nil
665+
}
663666
let head: RawExprSyntax
664667
if parser.at(.period) {
665668
head = parser.parseDottedExpressionSuffix(nil)
@@ -2236,7 +2239,7 @@ extension Parser {
22362239
elements.append(
22372240
.ifConfigDecl(
22382241
self.parsePoundIfDirective(
2239-
{ $0.parseSwitchCases(allowStandaloneStmtRecovery: allowStandaloneStmtRecovery) },
2242+
{ (parser, _) in parser.parseSwitchCases(allowStandaloneStmtRecovery: allowStandaloneStmtRecovery) },
22402243
syntax: { parser, cases in
22412244
guard cases.count == 1, let firstCase = cases.first else {
22422245
assert(cases.isEmpty)
@@ -2250,6 +2253,9 @@ extension Parser {
22502253
} else if allowStandaloneStmtRecovery && (self.atStartOfExpression() || self.atStartOfStatement() || self.atStartOfDeclaration()) {
22512254
// Synthesize a label for the stamenent or declaration that isn't coverd by a case right now.
22522255
let statements = parseSwitchCaseBody()
2256+
if statements.isEmpty {
2257+
break
2258+
}
22532259
elements.append(
22542260
.switchCase(
22552261
RawSwitchCaseSyntax(

Sources/SwiftParser/Lexer/Cursor.swift

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -695,16 +695,6 @@ extension Lexer.Cursor {
695695
mutating func advanceValidatingUTF8Character() -> Unicode.Scalar? {
696696
return Unicode.Scalar.lexing(advance: { self.advance() }, peek: { self.peek(at: 0) })
697697
}
698-
699-
/// Rever the lexer by `offset` bytes. This should only be used by `resetForSplit`.
700-
/// This must not back up by more bytes than the last token because that would
701-
/// require us to also update `previousTokenKind`, which we don't do in this
702-
/// function
703-
mutating func backUp(by offset: Int) {
704-
assert(!self.isAtStartOfFile)
705-
self.previous = self.input.baseAddress!.advanced(by: -(offset + 1)).pointee
706-
self.input = UnsafeBufferPointer(start: self.input.baseAddress!.advanced(by: -offset), count: self.input.count + offset)
707-
}
708698
}
709699

710700
// MARK: - Boundness of operators

Sources/SwiftParser/Lexer/LexemeSequence.swift

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,14 @@ extension Lexer {
6161
return self.nextToken
6262
}
6363

64+
/// Reset the lexeme sequence to the state we were in when lexing `splitToken`
65+
/// but after we consumed `consumedPrefix` bytes from `splitToken`.
6466
/// - Warning: Do not add more usages of this function.
65-
mutating func resetForSplit(of bytes: Int) -> Lexer.Lexeme {
66-
guard bytes > 0 else {
67-
return self.advance()
67+
mutating func resetForSplit(splitToken: Lexeme, consumedPrefix: Int) -> Lexer.Lexeme {
68+
self.cursor = splitToken.cursor
69+
for _ in 0..<consumedPrefix {
70+
_ = self.cursor.advance()
6871
}
69-
70-
// FIXME: This is kind of ridiculous. We shouldn't have to look backwards
71-
// in the token stream. We should be fusing together runs of operator and
72-
// identifier characters in the parser, not splitting and backing up
73-
// again in the lexer.
74-
let backUpLength = self.nextToken.byteLength + bytes
75-
self.cursor.backUp(by: backUpLength)
7672
self.nextToken = self.cursor.nextToken(sourceBufferStart: self.sourceBufferStart, stateAllocator: lexerStateAllocator)
7773
return self.advance()
7874
}

Sources/SwiftParser/Lookahead.swift

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,10 @@ extension Parser.Lookahead {
122122
}
123123
assert(tokenText.hasPrefix(prefix))
124124

125-
// See also: Parser.consumePrefix(_:as:)
126-
let offset =
127-
(self.currentToken.trailingTriviaByteLength
128-
+ tokenText.count
129-
- prefix.count)
130-
self.currentToken = self.lexemes.resetForSplit(of: offset)
125+
self.currentToken = self.lexemes.resetForSplit(
126+
splitToken: self.currentToken,
127+
consumedPrefix: self.currentToken.leadingTriviaByteLength + prefix.count
128+
)
131129
}
132130
}
133131

Sources/SwiftParser/Parser.swift

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -555,36 +555,10 @@ extension Parser {
555555

556556
self.adjustNestingLevel(for: tokenKind)
557557

558-
// ... or a multi-character token with the first N characters being the one
559-
// that we want to consume as a separate token.
560-
// Careful: We need to reset the lexer to a point just before it saw the
561-
// current token, plus the split point. That means we need to take trailing
562-
// trivia into account for the current token, but we only need to take the
563-
// number of UTF-8 bytes of the text of the split - no trivia necessary.
564-
//
565-
// <TOKEN<trailing trivia>> <NEXT TOKEN> ... -> <T> <OKEN<trailing trivia>> <NEXT TOKEN>
566-
//
567-
// The current calculation is:
568-
//
569-
// <<leading trivia>TOKEN<trailing trivia>>
570-
// CURSOR ^
571-
// + trailing trivia length
572-
//
573-
// <<leading trivia>TOKEN<trailing trivia>>
574-
// CURSOR ^
575-
// + content length
576-
//
577-
// <<leading trivia>TOKEN<trailing trivia>>
578-
// CURSOR ^
579-
// - split point length
580-
//
581-
// <<leading trivia>TOKEN<trailing trivia>>
582-
// CURSOR ^
583-
let offset =
584-
(self.currentToken.trailingTriviaByteLength
585-
+ tokenText.count
586-
- prefix.count)
587-
self.currentToken = self.lexemes.resetForSplit(of: offset)
558+
self.currentToken = self.lexemes.resetForSplit(
559+
splitToken: self.currentToken,
560+
consumedPrefix: self.currentToken.leadingTriviaByteLength + prefix.count
561+
)
588562
return tok
589563
}
590564
}

Sources/SwiftParser/StringLiterals.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,6 @@ extension Parser {
309309
// -------------------------------------------------------------------------
310310
// Precondition
311311

312-
assert(closeQuote.leadingTriviaByteLength == 0, "Closing quote produced by the lexer should not have leading trivia because we would drop it during post-processing")
313312
assert(
314313
allSegments.allSatisfy {
315314
if case .stringSegment(let segment) = $0 {
@@ -349,7 +348,7 @@ extension Parser {
349348
middleSegments: &middleSegments
350349
)
351350

352-
if !closeDelimiterOnNewLine {
351+
if !closeDelimiterOnNewLine || closeQuote.leadingTriviaByteLength != 0 {
353352
unexpectedBeforeCloseQuote = [closeQuote]
354353
closeQuote = RawTokenSyntax(missing: closeQuote.tokenKind, leadingTriviaPieces: [.newlines(1)], arena: self.arena)
355354

Sources/SwiftParser/TokenSpecSet.swift

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,6 @@ enum PrimaryExpressionStart: TokenSpecSet {
706706
enum ExpressionStart: TokenSpecSet {
707707
case awaitTryMove(AwaitTryMove)
708708
case expressionPrefixOperator(ExpressionPrefixOperator)
709-
case matchingPatternStart(MatchingPatternStart)
710709
case primaryExpressionStart(PrimaryExpressionStart)
711710
case ifOrSwitch(IfOrSwitch)
712711

@@ -715,8 +714,6 @@ enum ExpressionStart: TokenSpecSet {
715714
self = .awaitTryMove(subset)
716715
} else if let subset = ExpressionPrefixOperator(lexeme: lexeme) {
717716
self = .expressionPrefixOperator(subset)
718-
} else if let subset = MatchingPatternStart(lexeme: lexeme) {
719-
self = .matchingPatternStart(subset)
720717
} else if let subset = PrimaryExpressionStart(lexeme: lexeme) {
721718
self = .primaryExpressionStart(subset)
722719
} else if let subset = IfOrSwitch(lexeme: lexeme) {
@@ -729,7 +726,6 @@ enum ExpressionStart: TokenSpecSet {
729726
static var allCases: [ExpressionStart] {
730727
return AwaitTryMove.allCases.map(Self.awaitTryMove)
731728
+ ExpressionPrefixOperator.allCases.map(Self.expressionPrefixOperator)
732-
+ MatchingPatternStart.allCases.map(Self.matchingPatternStart)
733729
+ PrimaryExpressionStart.allCases.map(Self.primaryExpressionStart)
734730
+ IfOrSwitch.allCases.map(Self.ifOrSwitch)
735731
}
@@ -738,7 +734,6 @@ enum ExpressionStart: TokenSpecSet {
738734
switch self {
739735
case .awaitTryMove(let underlyingKind): return underlyingKind.spec
740736
case .expressionPrefixOperator(let underlyingKind): return underlyingKind.spec
741-
case .matchingPatternStart(let underlyingKind): return underlyingKind.spec
742737
case .primaryExpressionStart(let underlyingKind): return underlyingKind.spec
743738
case .ifOrSwitch(let underlyingKind): return underlyingKind.spec
744739
}

Sources/SwiftParser/TopLevel.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,8 @@ extension Parser {
228228
if self.at(.poundIfKeyword) && !self.withLookahead({ $0.consumeIfConfigOfAttributes() }) {
229229
// If config of attributes is parsed as part of declaration parsing as it
230230
// doesn't constitute its own code block item.
231-
let directive = self.parsePoundIfDirective {
232-
$0.parseCodeBlockItem()
231+
let directive = self.parsePoundIfDirective { (parser, _) in
232+
parser.parseCodeBlockItem()
233233
} addSemicolonIfNeeded: { lastElement, newItemAtStartOfLine, parser in
234234
if lastElement.semicolon == nil && !newItemAtStartOfLine {
235235
return RawCodeBlockItemSyntax(

Tests/SwiftParserTest/AttributeTests.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ final class AttributeTests: XCTestCase {
9090
func f(_: Int, _: Int, _: Int, _: Int, _: Int) { }
9191
"""
9292
)
93+
94+
AssertParse(
95+
"""
96+
@objc(_:)
97+
func f(_: Int)
98+
"""
99+
)
93100
}
94101

95102
func testRethrowsAttribute() {

Tests/SwiftParserTest/DirectiveTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,22 @@ final class DirectiveTests: XCTestCase {
6969
)
7070
}
7171

72+
func testPostfixIfConfigExpressionContainsPoundIf() {
73+
AssertParse(
74+
"""
75+
b
76+
#if true
77+
.a
78+
1️⃣#if true
79+
#endif
80+
#endif
81+
""",
82+
diagnostics: [
83+
DiagnosticSpec(message: "unexpected code in conditional compilation block")
84+
]
85+
)
86+
}
87+
7288
func testSourceLocation() {
7389
AssertParse(
7490
"""

0 commit comments

Comments
 (0)