Skip to content

Commit a74cceb

Browse files
committed
Add diagnostic for label with string segment
1 parent e85a14a commit a74cceb

32 files changed

+840
-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
@@ -1713,7 +1713,7 @@ public let DECL_NODES: [Node] = [
17131713
),
17141714
Child(
17151715
name: "FileName",
1716-
kind: .node(kind: .stringLiteralExpr),
1716+
kind: .node(kind: .simpleStringLiteralExpr),
17171717
nameForDiagnostics: "file name"
17181718
),
17191719
Child(

CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift

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

1500+
Node(
1501+
kind: .simpleStringLiteralExpr,
1502+
base: .expr,
1503+
nameForDiagnostics: "simple string literal",
1504+
documentation: "A simple string that can't be used for string interpolation or extended escaping.",
1505+
children: [
1506+
Child(
1507+
name: "OpenQuote",
1508+
kind: .token(choices: [.token(tokenKind: "StringQuoteToken"), .token(tokenKind: "MultilineStringQuoteToken")]),
1509+
documentation: "Open quote for the string literal"
1510+
),
1511+
Child(
1512+
name: "Segments",
1513+
kind: .collection(kind: .stringLiteralSegments, collectionElementName: "Segment"),
1514+
documentation: "String content"
1515+
),
1516+
Child(
1517+
name: "CloseQuote",
1518+
kind: .token(choices: [.token(tokenKind: "StringQuoteToken"), .token(tokenKind: "MultilineStringQuoteToken")]),
1519+
documentation: "Close quote for the string literal"
1520+
),
1521+
]
1522+
),
1523+
1524+
Node(
1525+
kind: .simpleStringLiteralSegments,
1526+
base: .syntaxCollection,
1527+
nameForDiagnostics: nil,
1528+
documentation: "String literal segments that only can contain non string interpolated or extended escaped strings",
1529+
elementChoices: [.stringSegment]
1530+
),
1531+
15001532
// string literal segment in a string interpolation expression.
15011533
Node(
15021534
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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,37 @@ 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: [SimpleStringLiteralSegmentsSyntax.Element] = []
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+
}
612+
}
613+
614+
let (unexpectedBetweenSegmentAndCloseQuote, closeQuote) = self.expect(anyIn: SimpleStringLiteralExprSyntax.OpenQuoteOptions.self, default: .stringQuote)
615+
let closeDelimiter = self.consume(if: .rawStringDelimiter)
616+
617+
return RawSimpleStringLiteralExprSyntax(
618+
RawUnexpectedNodesSyntax(combining: unexpectedBeforeOpenQuote, openDelimiter, arena: self.arena),
619+
openQuote: openQuote,
620+
segments: RawStringLiteralSegmentsSyntax(elements: segments, arena: self.arena),
621+
unexpectedBetweenSegmentAndCloseQuote,
622+
closeQuote: closeQuote,
623+
RawUnexpectedNodesSyntax([closeDelimiter], arena: self.arena),
624+
arena: self.arena
625+
)
626+
}
596627
}
597628

598629
// MARK: - Utilities

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: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,33 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
616616
return .visitChildren
617617
}
618618

619+
public override func visit(_ node: AvailabilityLabeledArgumentSyntax) -> SyntaxVisitorContinueKind {
620+
if shouldSkip(node) {
621+
return .skipChildren
622+
}
623+
624+
if case .string(let value) = node.value {
625+
let rawDelimiters: [TokenSyntax] = [value.unexpectedBeforeOpenQuote, value.unexpectedAfterCloseQuote]
626+
.compactMap { $0?.onlyPresentToken(where: { $0.tokenKind.isRawStringDelimiter }) }
627+
628+
if !rawDelimiters.isEmpty {
629+
addDiagnostic(
630+
node.label,
631+
ForbiddenExtendedEscapingString(token: node.label),
632+
fixIts: [
633+
FixIt(
634+
message: RemoveNodesFixIt(rawDelimiters),
635+
changes: rawDelimiters.map { .makeMissing($0) }
636+
)
637+
],
638+
handledNodes: rawDelimiters.map { $0.id }
639+
)
640+
}
641+
}
642+
643+
return .visitChildren
644+
}
645+
619646
public override func visit(_ node: AvailabilityVersionRestrictionSyntax) -> SyntaxVisitorContinueKind {
620647
if shouldSkip(node) {
621648
return .skipChildren
@@ -1465,6 +1492,33 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
14651492
return .visitChildren
14661493
}
14671494

1495+
public override func visit(_ node: PoundSourceLocationSyntax) -> SyntaxVisitorContinueKind {
1496+
if shouldSkip(node) {
1497+
return .skipChildren
1498+
}
1499+
1500+
if let args = node.args, args.fileName.hasError {
1501+
var rawDelimiters: [TokenSyntax] = []
1502+
if let unexpectedBeforeOpenQuote = args.fileName.unexpectedBeforeOpenQuote?.onlyPresentToken(where: { $0.tokenKind.isRawStringDelimiter }) {
1503+
rawDelimiters += [unexpectedBeforeOpenQuote]
1504+
}
1505+
1506+
if let unexpectedAfterCloseQuote = args.fileName.unexpectedAfterCloseQuote?.onlyPresentToken(where: { $0.tokenKind.isRawStringDelimiter }) {
1507+
rawDelimiters += [unexpectedAfterCloseQuote]
1508+
}
1509+
1510+
if !rawDelimiters.isEmpty {
1511+
addDiagnostic(
1512+
node,
1513+
ForbiddenExtendedEscapingString(token: args.fileLabel),
1514+
handledNodes: rawDelimiters.map { $0.id }
1515+
)
1516+
}
1517+
}
1518+
1519+
return .visitChildren
1520+
}
1521+
14681522
public override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind {
14691523
if shouldSkip(node) {
14701524
return .skipChildren

0 commit comments

Comments
 (0)