Skip to content

Commit 2179d9f

Browse files
authored
Merge pull request #1619 from ahoppen/ahoppen/unterminated-block-comment
Emit error for unterminated block comment
2 parents af14609 + e47d395 commit 2179d9f

File tree

6 files changed

+56
-33
lines changed

6 files changed

+56
-33
lines changed

Sources/SwiftParser/Lexer/Cursor.swift

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -684,24 +684,25 @@ extension Lexer.Cursor {
684684

685685
/// Returns `true` if the comment spaned multiple lines and `false` otherwise.
686686
/// Assumes that the curser is currently pointing at the `*` of the opening `/*`.
687-
mutating func advanceToEndOfSlashStarComment() -> Bool {
687+
mutating func advanceToEndOfSlashStarComment(slashPosition: Lexer.Cursor) -> TriviaResult {
688688
precondition(self.previous == UInt8(ascii: "/"))
689689
// Make sure to advance over the * so that we don't incorrectly handle /*/ as
690690
// the beginning and end of the comment.
691691
let consumedStar = self.advance(matching: "*")
692692
precondition(consumedStar)
693693

694694
var depth = 1
695-
var isMultiline = false
695+
var newlinePresence = NewlinePresence.absent
696+
var error: LexingDiagnostic? = nil
696697

697-
while true {
698+
LOOP: while true {
698699
switch self.advance() {
699700
case UInt8(ascii: "*"):
700701
// Check for a '*/'
701702
if self.advance(matching: "/") {
702703
depth -= 1
703704
if depth == 0 {
704-
return isMultiline
705+
break LOOP
705706
}
706707
}
707708
case UInt8(ascii: "/"):
@@ -711,14 +712,17 @@ extension Lexer.Cursor {
711712
}
712713

713714
case UInt8(ascii: "\n"), UInt8(ascii: "\r"):
714-
isMultiline = true
715+
newlinePresence = .present
715716
continue
716717
case nil:
717-
return isMultiline
718+
error = LexingDiagnostic(.unterminatedBlockComment, position: slashPosition)
719+
break LOOP
718720
case .some:
719721
continue
720722
}
721723
}
724+
725+
return TriviaResult(newlinePresence: newlinePresence, error: error)
722726
}
723727

724728
/// If this is the opening delimiter of a raw string literal, return the number
@@ -1063,7 +1067,7 @@ extension Lexer.Cursor {
10631067
// MARK: - Trivia
10641068

10651069
extension Lexer.Cursor {
1066-
fileprivate enum NewlinePresence {
1070+
enum NewlinePresence {
10671071
case absent
10681072
case present
10691073
}
@@ -1080,7 +1084,7 @@ extension Lexer.Cursor {
10801084
case escapedNewlineInMultiLineStringLiteral
10811085
}
10821086

1083-
fileprivate struct TriviaResult {
1087+
struct TriviaResult {
10841088
let newlinePresence: NewlinePresence
10851089
let error: LexingDiagnostic?
10861090
}
@@ -1137,7 +1141,11 @@ extension Lexer.Cursor {
11371141
self.advanceToEndOfLine()
11381142
continue
11391143
case UInt8(ascii: "*"):
1140-
_ = self.advanceToEndOfSlashStarComment()
1144+
let starSlashResult = self.advanceToEndOfSlashStarComment(slashPosition: start)
1145+
if starSlashResult.newlinePresence == .present {
1146+
newlinePresence = .present
1147+
}
1148+
error = error ?? starSlashResult.error
11411149
continue
11421150
default:
11431151
break

Sources/SwiftParser/TriviaParser.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ extension Lexer.Cursor {
191191
// "/**/": .blockComment.
192192
precondition(self.previous == UInt8(ascii: "/") && self.is(at: "*"))
193193
let isDocComment = self.input.count > 2 && self.is(offset: 1, at: "*") && self.is(offset: 2, notAt: "/")
194-
_ = self.advanceToEndOfSlashStarComment()
194+
_ = self.advanceToEndOfSlashStarComment(slashPosition: start)
195195
let contents = start.text(upTo: self)
196196
return isDocComment ? .docBlockComment(contents) : .blockComment(contents)
197197
}

Sources/SwiftParserDiagnostics/LexerDiagnosticMessages.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public enum StaticTokenError: String, DiagnosticMessage {
5858
case spaceAtEndOfRegexLiteral = "bare slash regex literal may not end with space"
5959
case multilineRegexClosingNotOnNewline = "multi-line regex closing delimiter must appear on new line"
6060
case unprintableAsciiCharacter = "unprintable ASCII character found in source file"
61+
case unterminatedBlockComment = "unterminated '/*' comment"
6162

6263
public var message: String { self.rawValue }
6364

@@ -160,16 +161,17 @@ public extension SwiftSyntax.TokenDiagnostic {
160161
case .invalidNumberOfHexDigitsInUnicodeEscape: return StaticTokenError.invalidNumberOfHexDigitsInUnicodeEscape
161162
case .invalidOctalDigitInIntegerLiteral: return InvalidDigitInIntegerLiteral(kind: .octal(scalarAtErrorOffset))
162163
case .invalidUtf8: return StaticTokenError.invalidUtf8
163-
case .tokenDiagnosticOffsetOverflow: return StaticTokenError.tokenDiagnosticOffsetOverflow
164+
case .multilineRegexClosingNotOnNewline: return StaticTokenError.multilineRegexClosingNotOnNewline
164165
case .nonBreakingSpace: return StaticTokenWarning.nonBreakingSpace
165166
case .nulCharacter: return StaticTokenWarning.nulCharacter
166167
case .sourceConflictMarker: return StaticTokenError.sourceConflictMarker
168+
case .spaceAtEndOfRegexLiteral: return StaticTokenError.spaceAtEndOfRegexLiteral
169+
case .spaceAtStartOfRegexLiteral: return StaticTokenError.spaceAtStartOfRegexLiteral
170+
case .tokenDiagnosticOffsetOverflow: return StaticTokenError.tokenDiagnosticOffsetOverflow
167171
case .unexpectedBlockCommentEnd: return StaticTokenError.unexpectedBlockCommentEnd
168172
case .unicodeCurlyQuote: return StaticTokenError.unicodeCurlyQuote
169-
case .spaceAtStartOfRegexLiteral: return StaticTokenError.spaceAtStartOfRegexLiteral
170-
case .spaceAtEndOfRegexLiteral: return StaticTokenError.spaceAtEndOfRegexLiteral
171-
case .multilineRegexClosingNotOnNewline: return StaticTokenError.multilineRegexClosingNotOnNewline
172173
case .unprintableAsciiCharacter: return StaticTokenError.unprintableAsciiCharacter
174+
case .unterminatedBlockComment: return StaticTokenError.unterminatedBlockComment
173175
}
174176
}
175177

Sources/SwiftSyntax/TokenDiagnostic.swift

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,18 @@ public struct TokenDiagnostic: Hashable {
4141
case invalidNumberOfHexDigitsInUnicodeEscape
4242
case invalidOctalDigitInIntegerLiteral
4343
case invalidUtf8
44-
/// The lexer dicovered an error but was not able to represent the offset of the error because it would overflow `LexerErrorOffset`.
45-
case tokenDiagnosticOffsetOverflow
44+
case multilineRegexClosingNotOnNewline
4645
case nonBreakingSpace
4746
case nulCharacter
4847
case sourceConflictMarker
48+
case spaceAtEndOfRegexLiteral
49+
case spaceAtStartOfRegexLiteral
50+
/// The lexer dicovered an error but was not able to represent the offset of the error because it would overflow `LexerErrorOffset`.
51+
case tokenDiagnosticOffsetOverflow
4952
case unexpectedBlockCommentEnd
5053
case unicodeCurlyQuote
5154
case unprintableAsciiCharacter
52-
case spaceAtStartOfRegexLiteral
53-
case spaceAtEndOfRegexLiteral
54-
case multilineRegexClosingNotOnNewline
55+
case unterminatedBlockComment
5556
}
5657

5758
public let kind: Kind
@@ -118,16 +119,17 @@ public struct TokenDiagnostic: Hashable {
118119
case .invalidNumberOfHexDigitsInUnicodeEscape: return .error
119120
case .invalidOctalDigitInIntegerLiteral: return .error
120121
case .invalidUtf8: return .error
121-
case .tokenDiagnosticOffsetOverflow: return .error
122+
case .multilineRegexClosingNotOnNewline: return .error
122123
case .nonBreakingSpace: return .warning
123124
case .nulCharacter: return .warning
124125
case .sourceConflictMarker: return .error
126+
case .spaceAtEndOfRegexLiteral: return .error
127+
case .spaceAtStartOfRegexLiteral: return .error
128+
case .tokenDiagnosticOffsetOverflow: return .error
125129
case .unexpectedBlockCommentEnd: return .error
126130
case .unicodeCurlyQuote: return .error
127131
case .unprintableAsciiCharacter: return .error
128-
case .spaceAtStartOfRegexLiteral: return .error
129-
case .spaceAtEndOfRegexLiteral: return .error
130-
case .multilineRegexClosingNotOnNewline: return .error
132+
case .unterminatedBlockComment: return .error
131133
}
132134
}
133135
}

Tests/SwiftParserTest/LexerTests.swift

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -562,9 +562,9 @@ public class LexerTests: XCTestCase {
562562
]
563563
)
564564
assertLexemes(
565-
"^/*/",
565+
"^1️⃣/*/",
566566
lexemes: [
567-
LexemeSpec(.binaryOperator, text: "^", trailing: "/*/")
567+
LexemeSpec(.binaryOperator, text: "^", trailing: "/*/", diagnostic: "unterminated '/*' comment")
568568
]
569569
)
570570
}
@@ -1461,4 +1461,22 @@ public class LexerTests: XCTestCase {
14611461
]
14621462
)
14631463
}
1464+
1465+
func testUnterminatedBlockComment() {
1466+
assertLexemes(
1467+
"1️⃣/*",
1468+
lexemes: [
1469+
LexemeSpec(.eof, leading: "/*", text: "", diagnostic: "unterminated '/*' comment")
1470+
]
1471+
)
1472+
}
1473+
1474+
func testSlashStartSlash() {
1475+
assertLexemes(
1476+
"1️⃣/*/",
1477+
lexemes: [
1478+
LexemeSpec(.eof, leading: "/*/", text: "", diagnostic: "unterminated '/*' comment")
1479+
]
1480+
)
1481+
}
14641482
}

Tests/SwiftParserTest/translated/GenericDisambiguationTests.swift

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,6 @@ final class GenericDisambiguationTests: XCTestCase {
195195
func testGenericDisambiguation12() {
196196
assertParse(
197197
"""
198-
// FIXME: Nested generic types. Need to be able to express $T0<A, B, C> in the
199-
// typechecker.
200-
/*
201198
A<B>.C<D>.e()
202199
"""
203200
)
@@ -216,11 +213,7 @@ final class GenericDisambiguationTests: XCTestCase {
216213
"""
217214
meta(A<B>.C<D>.self)
218215
meta2(A<B>.C<D>.self, 0)
219-
1️⃣*/
220-
""",
221-
diagnostics: [
222-
DiagnosticSpec(message: "extraneous code '*/' at top level")
223-
]
216+
"""
224217
)
225218
}
226219

0 commit comments

Comments
 (0)