Skip to content

Commit fdcb47f

Browse files
committed
Add diagnostic for label with string segment
1 parent 5a914f9 commit fdcb47f

32 files changed

+943
-57
lines changed

CodeGeneration/Sources/SyntaxSupport/AvailabilityNodes.swift

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

CodeGeneration/Sources/SyntaxSupport/DeclNodes.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1669,7 +1669,7 @@ public let DECL_NODES: [Node] = [
16691669
),
16701670
Child(
16711671
name: "FileName",
1672-
kind: .node(kind: .stringLiteralExpr),
1672+
kind: .node(kind: .simpleStringLiteralExpr),
16731673
nameForDiagnostics: "file name"
16741674
),
16751675
Child(

CodeGeneration/Sources/SyntaxSupport/ExprNodes.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1502,6 +1502,38 @@ public let EXPR_NODES: [Node] = [
15021502
elementChoices: [.stringSegment, .expressionSegment]
15031503
),
15041504

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

CodeGeneration/Sources/SyntaxSupport/SyntaxNodeKind.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ public enum SyntaxNodeKind: String, CaseIterable {
250250
case specializeAvailabilityArgument
251251
case specializeTargetFunctionArgument
252252
case stmt
253+
case simpleStringLiteralExpr
254+
case simpleStringLiteralSegments
253255
case stringLiteralExpr
254256
case stringLiteralSegmentList
255257
case stringSegment

Sources/SwiftBasicFormat/BasicFormat.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ open class BasicFormat: SyntaxRewriter {
347347
case \ExpressionSegmentSyntax.backslash,
348348
\ExpressionSegmentSyntax.rightParen,
349349
\DeclNameArgumentSyntax.colon,
350+
\SimpleStringLiteralExprSyntax.openingQuote,
350351
\StringLiteralExprSyntax.openingQuote,
351352
\RegexLiteralExprSyntax.openingSlash:
352353
return false

Sources/SwiftParser/Availability.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ extension Parser {
102102
(.renamed, let handle)?:
103103
let argumentLabel = self.eat(handle)
104104
let (unexpectedBeforeColon, colon) = self.expect(.colon)
105-
// FIXME: Make sure this is a string literal with no interpolation.
106-
let stringValue = self.parseStringLiteral()
105+
let stringValue = self.parseSimpleString()
107106

108107
entry = .availabilityLabeledArgument(
109108
RawAvailabilityLabeledArgumentSyntax(

Sources/SwiftParser/Directives.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ extension Parser {
196196
if !self.at(.rightParen) {
197197
let (unexpectedBeforeFile, file) = self.expect(.keyword(.file))
198198
let (unexpectedBeforeFileColon, fileColon) = self.expect(.colon)
199-
let fileName = self.parseStringLiteral()
199+
let fileName = self.parseSimpleString()
200200
let (unexpectedBeforeComma, comma) = self.expect(.comma)
201201

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

Sources/SwiftParser/StringLiterals.swift

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ extension Parser {
147147
return RawTokenSyntax(
148148
kind: token.tokenKind,
149149
text: SyntaxText(rebasing: token.tokenText.dropFirst(reclassifyLeading.count).dropLast(reclassifyTrailing.count)),
150-
leadingTriviaPieces: token.leadingTriviaPieces + TriviaParser.parseTrivia(reclassifyLeading, position: .trailing),
150+
leadingTriviaPieces: token.leadingTriviaPieces + TriviaParser.parseTrivia(reclassifyLeading, position: .leading),
151151
trailingTriviaPieces: TriviaParser.parseTrivia(reclassifyTrailing, position: .trailing) + token.trailingTriviaPieces,
152152
presence: token.presence,
153153
tokenDiagnostic: token.tokenView.tokenDiagnostic ?? tokenDiagnostic,
@@ -595,10 +595,105 @@ extension Parser {
595595
)
596596
}
597597
}
598+
599+
mutating func parseSimpleString() -> RawSimpleStringLiteralExprSyntax {
600+
let openDelimiter = self.consume(if: .rawStringPoundDelimiter)
601+
let (unexpectedBeforeOpenQuote, openQuote) = self.expect(anyIn: SimpleStringLiteralExprSyntax.OpeningQuoteOptions.self, default: .stringQuote)
602+
603+
/// Parse segments.
604+
var segments: [RawStringSegmentSyntax] = []
605+
var loopProgress = LoopProgressCondition()
606+
while hasProgressed(&loopProgress) {
607+
// If we encounter a token with leading trivia, we're no longer in the
608+
// string literal.
609+
guard currentToken.leadingTriviaText.isEmpty else { break }
610+
611+
if let stringSegment = self.consume(if: .stringSegment, TokenSpec(.identifier, remapping: .stringSegment)) {
612+
var unexpectedAfterContent: RawUnexpectedNodesSyntax?
613+
614+
if let (backslash, leftParen) = self.consume(if: .backslash, followedBy: .leftParen) {
615+
var unexpectedTokens: [RawSyntax] = [RawSyntax(backslash), RawSyntax(leftParen)]
616+
617+
let (unexpectedBeforeRightParen, rightParen) = self.expect(TokenSpec(.rightParen, allowAtStartOfLine: false))
618+
unexpectedTokens += unexpectedBeforeRightParen?.elements ?? []
619+
unexpectedTokens.append(RawSyntax(rightParen))
620+
621+
unexpectedAfterContent = RawUnexpectedNodesSyntax(
622+
unexpectedTokens,
623+
arena: self.arena
624+
)
625+
}
626+
627+
segments.append(RawStringSegmentSyntax(content: stringSegment, unexpectedAfterContent, arena: self.arena))
628+
} else {
629+
break
630+
}
631+
}
632+
633+
let (unexpectedBetweenSegmentAndCloseQuote, closeQuote) = self.expect(
634+
anyIn: SimpleStringLiteralExprSyntax.ClosingQuoteOptions.self,
635+
default: openQuote.closeTokenKind
636+
)
637+
let closeDelimiter = self.consume(if: .rawStringPoundDelimiter)
638+
639+
if openQuote.tokenKind == .multilineStringQuote, !openQuote.isMissing, !closeQuote.isMissing {
640+
let postProcessed = postProcessMultilineStringLiteral(
641+
rawStringDelimitersToken: openDelimiter,
642+
openQuote: openQuote,
643+
segments: segments.compactMap { RawStringLiteralSegmentListSyntax.Element.stringSegment($0) },
644+
closeQuote: closeQuote
645+
)
646+
647+
return RawSimpleStringLiteralExprSyntax(
648+
RawUnexpectedNodesSyntax(
649+
combining: unexpectedBeforeOpenQuote,
650+
postProcessed.unexpectedBeforeOpeningQuote,
651+
openDelimiter,
652+
arena: self.arena
653+
),
654+
openingQuote: postProcessed.openingQuote,
655+
segments: RawSimpleStringLiteralSegmentsSyntax(
656+
elements: postProcessed.segments.compactMap { $0.as(RawStringSegmentSyntax.self) },
657+
arena: self.arena
658+
),
659+
unexpectedBetweenSegmentAndCloseQuote,
660+
closingQuote: postProcessed.closingQuote,
661+
RawUnexpectedNodesSyntax(
662+
combining: closeDelimiter,
663+
postProcessed.unexpectedBeforeClosingQuote,
664+
arena: self.arena
665+
),
666+
arena: self.arena
667+
)
668+
} else {
669+
return RawSimpleStringLiteralExprSyntax(
670+
RawUnexpectedNodesSyntax(combining: unexpectedBeforeOpenQuote, openDelimiter, arena: self.arena),
671+
openingQuote: openQuote,
672+
segments: RawSimpleStringLiteralSegmentsSyntax(elements: segments, arena: self.arena),
673+
unexpectedBetweenSegmentAndCloseQuote,
674+
closingQuote: closeQuote,
675+
RawUnexpectedNodesSyntax([closeDelimiter], arena: self.arena),
676+
arena: self.arena
677+
)
678+
}
679+
}
598680
}
599681

600682
// MARK: - Utilities
601683

684+
fileprivate extension RawTokenSyntax {
685+
var closeTokenKind: SimpleStringLiteralExprSyntax.ClosingQuoteOptions {
686+
switch self {
687+
case .multilineStringQuote:
688+
return .multilineStringQuote
689+
case .stringQuote:
690+
return .stringQuote
691+
default:
692+
return .stringQuote
693+
}
694+
}
695+
}
696+
602697
fileprivate extension SyntaxText {
603698
private func hasSuffix(_ other: String) -> Bool {
604699
var other = other

Sources/SwiftParser/generated/Parser+TokenSpecSet.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1691,6 +1691,60 @@ extension SameTypeRequirementSyntax {
16911691
}
16921692
}
16931693

1694+
extension SimpleStringLiteralExprSyntax {
1695+
enum OpeningQuoteOptions: TokenSpecSet {
1696+
case stringQuote
1697+
case multilineStringQuote
1698+
1699+
init?(lexeme: Lexer.Lexeme) {
1700+
switch PrepareForKeywordMatch(lexeme) {
1701+
case TokenSpec(.stringQuote):
1702+
self = .stringQuote
1703+
case TokenSpec(.multilineStringQuote):
1704+
self = .multilineStringQuote
1705+
default:
1706+
return nil
1707+
}
1708+
}
1709+
1710+
var spec: TokenSpec {
1711+
switch self {
1712+
case .stringQuote:
1713+
return .stringQuote
1714+
case .multilineStringQuote:
1715+
return .multilineStringQuote
1716+
}
1717+
}
1718+
}
1719+
}
1720+
1721+
extension SimpleStringLiteralExprSyntax {
1722+
enum ClosingQuoteOptions: TokenSpecSet {
1723+
case stringQuote
1724+
case multilineStringQuote
1725+
1726+
init?(lexeme: Lexer.Lexeme) {
1727+
switch PrepareForKeywordMatch(lexeme) {
1728+
case TokenSpec(.stringQuote):
1729+
self = .stringQuote
1730+
case TokenSpec(.multilineStringQuote):
1731+
self = .multilineStringQuote
1732+
default:
1733+
return nil
1734+
}
1735+
}
1736+
1737+
var spec: TokenSpec {
1738+
switch self {
1739+
case .stringQuote:
1740+
return .stringQuote
1741+
case .multilineStringQuote:
1742+
return .multilineStringQuote
1743+
}
1744+
}
1745+
}
1746+
}
1747+
16941748
extension SomeOrAnyTypeSyntax {
16951749
enum SomeOrAnySpecifierOptions: TokenSpecSet {
16961750
case some

Sources/SwiftParserDiagnostics/ParseDiagnosticsGenerator.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,6 +1485,56 @@ public class ParseDiagnosticsGenerator: SyntaxAnyVisitor {
14851485
return .visitChildren
14861486
}
14871487

1488+
public override func visit(_ node: SimpleStringLiteralExprSyntax) -> SyntaxVisitorContinueKind {
1489+
if shouldSkip(node) {
1490+
return .skipChildren
1491+
}
1492+
1493+
var rawDelimiters: [TokenSyntax] = []
1494+
1495+
if let unexpectedBeforeOpenQuote = node.unexpectedBeforeOpenQuote?.onlyPresentToken(where: { $0.tokenKind.isRawStringDelimiter }) {
1496+
rawDelimiters += [unexpectedBeforeOpenQuote]
1497+
}
1498+
1499+
if let unexpectedAfterCloseQuote = node.unexpectedAfterCloseQuote?.onlyPresentToken(where: { $0.tokenKind.isRawStringDelimiter }) {
1500+
rawDelimiters += [unexpectedAfterCloseQuote]
1501+
}
1502+
1503+
if !rawDelimiters.isEmpty {
1504+
addDiagnostic(
1505+
node,
1506+
.forbiddenExtendedEscapingString,
1507+
fixIts: [
1508+
FixIt(
1509+
message: RemoveNodesFixIt(rawDelimiters),
1510+
changes: rawDelimiters.map { .makeMissing($0) }
1511+
)
1512+
],
1513+
handledNodes: rawDelimiters.map { $0.id }
1514+
)
1515+
}
1516+
1517+
return .visitChildren
1518+
}
1519+
1520+
public override func visit(_ node: SimpleStringLiteralSegmentsSyntax) -> SyntaxVisitorContinueKind {
1521+
if shouldSkip(node) {
1522+
return .skipChildren
1523+
}
1524+
1525+
for segment in node {
1526+
if let unexpectedAfterContent = segment.unexpectedAfterContent {
1527+
addDiagnostic(
1528+
node,
1529+
.forbiddenInterpolatedString,
1530+
handledNodes: [unexpectedAfterContent.id]
1531+
)
1532+
}
1533+
}
1534+
1535+
return .visitChildren
1536+
}
1537+
14881538
public override func visit(_ node: SourceFileSyntax) -> SyntaxVisitorContinueKind {
14891539
if shouldSkip(node) {
14901540
return .skipChildren

0 commit comments

Comments
 (0)