Skip to content

Commit e5cc28a

Browse files
committed
Add diagnostic for label with string segment
1 parent e7db129 commit e5cc28a

32 files changed

+830
-41
lines changed

CodeGeneration/Sources/SyntaxSupport/AvailabilityNodes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public let AVAILABILITY_NODES: [Node] = [
8484
kind: .nodeChoices(choices: [
8585
Child(
8686
name: "String",
87-
kind: .node(kind: .stringLiteralExpr)
87+
kind: .node(kind: .simpleStringLiteralExpr)
8888
),
8989
Child(
9090
name: "Version",

CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1716,7 +1716,7 @@ public let DECL_NODES: [Node] = [
17161716
),
17171717
Child(
17181718
name: "FileName",
1719-
kind: .node(kind: .stringLiteralExpr),
1719+
kind: .node(kind: .simpleStringLiteralExpr),
17201720
nameForDiagnostics: "file name"
17211721
),
17221722
Child(

CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,38 @@ public let EXPR_NODES: [Node] = [
14991499
elementChoices: [.stringSegment, .expressionSegment]
15001500
),
15011501

1502+
Node(
1503+
kind: .simpleStringLiteralExpr,
1504+
base: .expr,
1505+
nameForDiagnostics: "simple string literal",
1506+
documentation: "A simple string that can’t contain string interpolation and cannot have raw string delimiters.",
1507+
children: [
1508+
Child(
1509+
name: "OpenQuote",
1510+
kind: .token(choices: [.token(tokenKind: "StringQuoteToken"), .token(tokenKind: "MultilineStringQuoteToken")]),
1511+
documentation: "Open quote for the string literal"
1512+
),
1513+
Child(
1514+
name: "Segments",
1515+
kind: .collection(kind: .simpleStringLiteralSegments, collectionElementName: "Segment"),
1516+
documentation: "String content"
1517+
),
1518+
Child(
1519+
name: "CloseQuote",
1520+
kind: .token(choices: [.token(tokenKind: "StringQuoteToken"), .token(tokenKind: "MultilineStringQuoteToken")]),
1521+
documentation: "Close quote for the string literal"
1522+
),
1523+
]
1524+
),
1525+
1526+
Node(
1527+
kind: .simpleStringLiteralSegments,
1528+
base: .syntaxCollection,
1529+
nameForDiagnostics: nil,
1530+
documentation: "String literal segments that only can contain non string interpolated or extended escaped strings",
1531+
elementChoices: [.stringSegment]
1532+
),
1533+
15021534
// string literal segment in a string interpolation expression.
15031535
Node(
15041536
kind: .stringSegment,

CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,8 @@ public enum SyntaxNodeKind: String, CaseIterable {
251251
case specializeAttributeSpecList
252252
case specializeExpr
253253
case stmt
254+
case simpleStringLiteralExpr
255+
case simpleStringLiteralSegments
254256
case stringLiteralExpr
255257
case stringLiteralSegments
256258
case stringSegment

Sources/SwiftBasicFormat/BasicFormat.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ open class BasicFormat: SyntaxRewriter {
282282
case \ExpressionSegmentSyntax.backslash,
283283
\ExpressionSegmentSyntax.rightParen,
284284
\DeclNameArgumentSyntax.colon,
285+
\SimpleStringLiteralExprSyntax.openQuote,
285286
\StringLiteralExprSyntax.openQuote,
286287
\RegexLiteralExprSyntax.openSlash:
287288
return false

Sources/SwiftParser/Availability.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,7 @@ extension Parser {
107107
(.renamed, let handle)?:
108108
let argumentLabel = self.eat(handle)
109109
let (unexpectedBeforeColon, colon) = self.expect(.colon)
110-
// FIXME: Make sure this is a string literal with no interpolation.
111-
let stringValue = self.parseStringLiteral()
110+
let stringValue = self.parseSimpleString()
112111

113112
entry = .availabilityLabeledArgument(
114113
RawAvailabilityLabeledArgumentSyntax(

Sources/SwiftParser/Directives.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ extension Parser {
230230
if !self.at(.rightParen) {
231231
let (unexpectedBeforeFile, file) = self.expect(.keyword(.file))
232232
let (unexpectedBeforeFileColon, fileColon) = self.expect(.colon)
233-
let fileName = self.parseStringLiteral()
233+
let fileName = self.parseSimpleString()
234234
let (unexpectedBeforeComma, comma) = self.expect(.comma)
235235

236236
let (unexpectedBeforeLine, line) = self.expect(.keyword(.line))

Sources/SwiftParser/StringLiterals.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,10 +593,59 @@ extension Parser {
593593
)
594594
}
595595
}
596+
597+
mutating func parseSimpleString() -> RawSimpleStringLiteralExprSyntax {
598+
let openDelimiter = self.consume(if: .rawStringDelimiter)
599+
let (unexpectedBeforeOpenQuote, openQuote) = self.expect(anyIn: SimpleStringLiteralExprSyntax.OpenQuoteOptions.self, default: .stringQuote)
600+
601+
/// Parse segments.
602+
var segments: [RawStringSegmentSyntax] = []
603+
var loopProgress = LoopProgressCondition()
604+
while loopProgress.evaluate(self.currentToken) {
605+
// If we encounter a token with leading trivia, we're no longer in the
606+
// string literal.
607+
guard currentToken.leadingTriviaText.isEmpty else { break }
608+
609+
if let stringSegment = self.consume(if: .stringSegment, TokenSpec(.identifier, remapping: .stringSegment)) {
610+
segments.append(RawStringSegmentSyntax(content: stringSegment, arena: self.arena))
611+
} else {
612+
break
613+
}
614+
}
615+
616+
let (unexpectedBetweenSegmentAndCloseQuote, closeQuote) = self.expect(
617+
anyIn: SimpleStringLiteralExprSyntax.CloseQuoteOptions.self,
618+
default: openQuote.closeTokenKind
619+
)
620+
let closeDelimiter = self.consume(if: .rawStringDelimiter)
621+
622+
return RawSimpleStringLiteralExprSyntax(
623+
RawUnexpectedNodesSyntax(combining: unexpectedBeforeOpenQuote, openDelimiter, arena: self.arena),
624+
openQuote: openQuote,
625+
segments: RawSimpleStringLiteralSegmentsSyntax(elements: segments, arena: self.arena),
626+
unexpectedBetweenSegmentAndCloseQuote,
627+
closeQuote: closeQuote,
628+
RawUnexpectedNodesSyntax([closeDelimiter], arena: self.arena),
629+
arena: self.arena
630+
)
631+
}
596632
}
597633

598634
// MARK: - Utilities
599635

636+
fileprivate extension RawTokenSyntax {
637+
var closeTokenKind: SimpleStringLiteralExprSyntax.CloseQuoteOptions {
638+
switch self {
639+
case .multilineStringQuote:
640+
return .multilineStringQuote
641+
case .stringQuote:
642+
return .stringQuote
643+
default:
644+
fatalError("Unsupported type")
645+
}
646+
}
647+
}
648+
600649
fileprivate extension SyntaxText {
601650
private func hasSuffix(_ other: String) -> Bool {
602651
var other = other

Sources/SwiftParser/generated/Parser+TokenSpecSet.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1780,6 +1780,60 @@ extension SameTypeRequirementSyntax {
17801780
}
17811781
}
17821782

1783+
extension SimpleStringLiteralExprSyntax {
1784+
enum OpenQuoteOptions: TokenSpecSet {
1785+
case stringQuote
1786+
case multilineStringQuote
1787+
1788+
init?(lexeme: Lexer.Lexeme) {
1789+
switch PrepareForKeywordMatch(lexeme) {
1790+
case TokenSpec(.stringQuote):
1791+
self = .stringQuote
1792+
case TokenSpec(.multilineStringQuote):
1793+
self = .multilineStringQuote
1794+
default:
1795+
return nil
1796+
}
1797+
}
1798+
1799+
var spec: TokenSpec {
1800+
switch self {
1801+
case .stringQuote:
1802+
return .stringQuote
1803+
case .multilineStringQuote:
1804+
return .multilineStringQuote
1805+
}
1806+
}
1807+
}
1808+
}
1809+
1810+
extension SimpleStringLiteralExprSyntax {
1811+
enum CloseQuoteOptions: TokenSpecSet {
1812+
case stringQuote
1813+
case multilineStringQuote
1814+
1815+
init?(lexeme: Lexer.Lexeme) {
1816+
switch PrepareForKeywordMatch(lexeme) {
1817+
case TokenSpec(.stringQuote):
1818+
self = .stringQuote
1819+
case TokenSpec(.multilineStringQuote):
1820+
self = .multilineStringQuote
1821+
default:
1822+
return nil
1823+
}
1824+
}
1825+
1826+
var spec: TokenSpec {
1827+
switch self {
1828+
case .stringQuote:
1829+
return .stringQuote
1830+
case .multilineStringQuote:
1831+
return .multilineStringQuote
1832+
}
1833+
}
1834+
}
1835+
}
1836+
17831837
extension SimpleTypeIdentifierSyntax {
17841838
enum NameOptions: TokenSpecSet {
17851839
case identifier

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1469,6 +1469,32 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
14691469
return .visitChildren
14701470
}
14711471

1472+
public override func visit(_ node: SimpleStringLiteralExprSyntax) -> SyntaxVisitorContinueKind {
1473+
if shouldSkip(node) {
1474+
return .skipChildren
1475+
}
1476+
1477+
var rawDelimiters: [TokenSyntax] = []
1478+
1479+
if let unexpectedBeforeOpenQuote = node.unexpectedBeforeOpenQuote?.onlyPresentToken(where: { $0.tokenKind.isRawStringDelimiter }) {
1480+
rawDelimiters += [unexpectedBeforeOpenQuote]
1481+
}
1482+
1483+
if let unexpectedAfterCloseQuote = node.unexpectedAfterCloseQuote?.onlyPresentToken(where: { $0.tokenKind.isRawStringDelimiter }) {
1484+
rawDelimiters += [unexpectedAfterCloseQuote]
1485+
}
1486+
1487+
if !rawDelimiters.isEmpty {
1488+
addDiagnostic(
1489+
node,
1490+
ForbiddenExtendedEscapingString(token: node.previousToken(viewMode: .sourceAccurate)!),
1491+
handledNodes: rawDelimiters.map { $0.id }
1492+
)
1493+
}
1494+
1495+
return .visitChildren
1496+
}
1497+
14721498
public override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind {
14731499
if shouldSkip(node) {
14741500
return .skipChildren

0 commit comments

Comments
 (0)