Skip to content

Commit 222b7f4

Browse files
authored
[5.9] Fix newline parsing at trailing trivia (#1671)
1 parent a63faab commit 222b7f4

File tree

3 files changed

+51
-16
lines changed

3 files changed

+51
-16
lines changed

Sources/SwiftParser/Lexer/Cursor.swift

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,9 @@ extension Lexer {
243243
/// If we have already lexed a token, the kind of the previously lexed token
244244
var previousTokenKind: RawTokenKind?
245245

246+
/// If we have already lexed a token, stores whether the previous lexeme‘s ending contains a newline.
247+
var previousLexemeTrailingNewlinePresence: NewlinePresence?
248+
246249
/// If the `previousTokenKind` is `.keyword`, the keyword kind. Otherwise
247250
/// `nil`.
248251
var previousKeyword: Keyword?
@@ -318,21 +321,25 @@ extension Lexer {
318321
/// If `tokenKind` is `.keyword`, the kind of keyword produced, otherwise
319322
/// `nil`.
320323
let keywordKind: Keyword?
324+
/// Indicates whether the end of the lexed token text contains a newline.
325+
let trailingNewlinePresence: Lexer.Cursor.NewlinePresence
321326

322327
private init(
323328
_ tokenKind: RawTokenKind,
324329
flags: Lexer.Lexeme.Flags,
325330
error: Cursor.LexingDiagnostic?,
326331
stateTransition: StateTransition?,
327332
trailingTriviaLexingMode: Lexer.Cursor.TriviaLexingMode?,
328-
keywordKind: Keyword?
333+
keywordKind: Keyword?,
334+
trailingNewlinePresence: Lexer.Cursor.NewlinePresence
329335
) {
330336
self.tokenKind = tokenKind
331337
self.flags = flags
332338
self.error = error
333339
self.stateTransition = stateTransition
334340
self.trailingTriviaLexingMode = trailingTriviaLexingMode
335341
self.keywordKind = keywordKind
342+
self.trailingNewlinePresence = trailingNewlinePresence
336343
}
337344

338345
/// Create a lexer result. Note that keywords should use `Result.keyword`
@@ -342,7 +349,8 @@ extension Lexer {
342349
flags: Lexer.Lexeme.Flags = [],
343350
error: Cursor.LexingDiagnostic? = nil,
344351
stateTransition: StateTransition? = nil,
345-
trailingTriviaLexingMode: Lexer.Cursor.TriviaLexingMode? = nil
352+
trailingTriviaLexingMode: Lexer.Cursor.TriviaLexingMode? = nil,
353+
trailingNewlinePresence: Lexer.Cursor.NewlinePresence = .absent
346354
) {
347355
precondition(tokenKind != .keyword, "Use Result.keyword instead")
348356
self.init(
@@ -351,7 +359,8 @@ extension Lexer {
351359
error: error,
352360
stateTransition: stateTransition,
353361
trailingTriviaLexingMode: trailingTriviaLexingMode,
354-
keywordKind: nil
362+
keywordKind: nil,
363+
trailingNewlinePresence: trailingNewlinePresence
355364
)
356365
}
357366

@@ -363,7 +372,8 @@ extension Lexer {
363372
error: nil,
364373
stateTransition: nil,
365374
trailingTriviaLexingMode: nil,
366-
keywordKind: kind
375+
keywordKind: kind,
376+
trailingNewlinePresence: .absent
367377
)
368378
}
369379
}
@@ -431,6 +441,16 @@ extension Lexer.Cursor {
431441
result = lexInRegexLiteral(lexemes.pointee[index...], existingPtr: lexemes)
432442
}
433443

444+
var flags = result.flags
445+
if newlineInLeadingTrivia == .present {
446+
flags.insert(.isAtStartOfLine)
447+
}
448+
if let previousLexemeTrailingNewlinePresence, previousLexemeTrailingNewlinePresence == .present {
449+
flags.insert(.isAtStartOfLine)
450+
}
451+
452+
self.previousLexemeTrailingNewlinePresence = result.trailingNewlinePresence
453+
434454
if let stateTransition = result.stateTransition {
435455
self.stateStack.perform(stateTransition: stateTransition, stateAllocator: stateAllocator)
436456
}
@@ -439,18 +459,14 @@ extension Lexer.Cursor {
439459
let trailingTriviaStart = self
440460
if let trailingTriviaMode = result.trailingTriviaLexingMode ?? currentState.trailingTriviaLexingMode(cursor: self) {
441461
let triviaResult = self.lexTrivia(mode: trailingTriviaMode)
462+
self.previousLexemeTrailingNewlinePresence = triviaResult.newlinePresence
442463
diagnostic = TokenDiagnostic(combining: diagnostic, triviaResult.error?.tokenDiagnostic(tokenStart: cursor))
443464
}
444465

445466
if self.currentState.shouldPopStateWhenReachingNewlineInTrailingTrivia && self.is(at: "\r", "\n") {
446467
self.stateStack.perform(stateTransition: .pop, stateAllocator: stateAllocator)
447468
}
448469

449-
var flags = result.flags
450-
if newlineInLeadingTrivia == .present {
451-
flags.insert(.isAtStartOfLine)
452-
}
453-
454470
diagnostic = TokenDiagnostic(combining: diagnostic, result.error?.tokenDiagnostic(tokenStart: cursor))
455471

456472
let lexeme = Lexer.Lexeme(
@@ -1890,7 +1906,7 @@ extension Lexer.Cursor {
18901906
if character == UInt8(ascii: "\r") {
18911907
_ = self.advance(matching: "\n")
18921908
}
1893-
return Lexer.Result(.stringSegment, error: error)
1909+
return Lexer.Result(.stringSegment, error: error, trailingNewlinePresence: .present)
18941910
} else {
18951911
// Single line literals cannot span multiple lines.
18961912
// Terminate the string here and go back to normal lexing (instead of `afterStringLiteral`)

Tests/SwiftParserTest/LexerTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,9 +1182,9 @@ public class LexerTests: XCTestCase {
11821182
"""#,
11831183
lexemes: [
11841184
LexemeSpec(.multilineStringQuote, leading: " ", text: #"""""#, trailing: "\n"),
1185-
LexemeSpec(.stringSegment, text: " line 1\n"),
1186-
LexemeSpec(.stringSegment, text: " line 2\n"),
1187-
LexemeSpec(.stringSegment, text: " "),
1185+
LexemeSpec(.stringSegment, text: " line 1\n", flags: .isAtStartOfLine),
1186+
LexemeSpec(.stringSegment, text: " line 2\n", flags: .isAtStartOfLine),
1187+
LexemeSpec(.stringSegment, text: " ", flags: .isAtStartOfLine),
11881188
LexemeSpec(.multilineStringQuote, text: #"""""#),
11891189
]
11901190
)
@@ -1198,9 +1198,9 @@ public class LexerTests: XCTestCase {
11981198
"""#,
11991199
lexemes: [
12001200
LexemeSpec(.multilineStringQuote, leading: " ", text: #"""""#, trailing: "\n"),
1201-
LexemeSpec(.stringSegment, text: " line 1 ", trailing: "\\\n"),
1202-
LexemeSpec(.stringSegment, text: " line 2\n"),
1203-
LexemeSpec(.stringSegment, text: " "),
1201+
LexemeSpec(.stringSegment, text: " line 1 ", trailing: "\\\n", flags: .isAtStartOfLine),
1202+
LexemeSpec(.stringSegment, text: " line 2\n", flags: .isAtStartOfLine),
1203+
LexemeSpec(.stringSegment, text: " ", flags: .isAtStartOfLine),
12041204
LexemeSpec(.multilineStringQuote, text: #"""""#),
12051205
]
12061206
)

Tests/SwiftParserTest/StatementTests.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,4 +718,23 @@ final class StatementTests: XCTestCase {
718718
]
719719
)
720720
}
721+
722+
func testTrailingTriviaIncludesNewline() {
723+
assertParse(
724+
"""
725+
let a = 2/*
726+
*/let b = 3
727+
"""
728+
)
729+
730+
assertParse(
731+
"""
732+
let a = 2/*
733+
734+
735+
736+
*/let b = 3
737+
"""
738+
)
739+
}
721740
}

0 commit comments

Comments
 (0)