Skip to content

Commit 8957ef3

Browse files
authored
Merge pull request #1802 from ahoppen/ahoppen/5.9/keyword-freestanding-macro-names
[5.9] Allow keywords after `#` in freestanding macro expansions
2 parents ddcc9b2 + 152ad76 commit 8957ef3

File tree

5 files changed

+57
-43
lines changed

5 files changed

+57
-43
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2048,7 +2048,7 @@ extension Parser {
20482048
arena: self.arena
20492049
)
20502050
}
2051-
let (unexpectedBeforeMacro, macro) = self.expectIdentifier(keywordRecovery: true)
2051+
let (unexpectedBeforeMacro, macro) = self.expectIdentifier(allowKeywordsAsIdentifier: true)
20522052

20532053
// Parse the optional generic argument list.
20542054
let generics: RawGenericArgumentClauseSyntax?

Sources/SwiftParser/Expressions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1378,7 +1378,7 @@ extension Parser {
13781378
)
13791379
}
13801380
let poundKeyword = self.consumeAnyToken()
1381-
let (unexpectedBeforeMacro, macro) = self.expectIdentifier(keywordRecovery: true)
1381+
let (unexpectedBeforeMacro, macro) = self.expectIdentifier(allowKeywordsAsIdentifier: true)
13821382

13831383
// Parse the optional generic argument list.
13841384
let generics: RawGenericArgumentClauseSyntax?

Sources/SwiftParser/Parser.swift

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -431,24 +431,32 @@ extension Parser {
431431
)
432432
}
433433

434-
/// If `keywordRecovery` is set to `true` and the parser is currently
435-
/// positioned at a keyword instead of an identifier, this method recovers by
436-
/// eating the keyword in place of an identifier, recovering if the developer
437-
/// incorrectly used a keyword as an identifier.
438-
/// This should be set if keywords aren't strong recovery marker at this
439-
/// position, e.g. because the parser expects a punctuator next.
440-
///
441-
/// If `allowSelfOrCapitalSelfAsIdentifier` is `true`, then `self` and `Self`
442-
/// are also accepted and remapped to identifiers. This is exclusively used
443-
/// to maintain compatibility with the C++ parser. No new uses of this should
444-
/// be introduced.
434+
/// - Parameters:
435+
/// - keywordRecovery: If set to `true` and the parser is currently
436+
/// positioned at a keyword instead of an identifier, this method recovers
437+
/// by eating the keyword in place of an identifier, recovering if the
438+
/// developer incorrectly used a keyword as an identifier. This should be
439+
/// set if keywords aren't strong recovery marker at this position, e.g.
440+
/// because the parser expects a punctuator next
441+
/// - allowSelfOrCapitalSelfAsIdentifier: If set to `true`, then `self` and
442+
/// `Self` are also accepted and remapped to identifiers. This is
443+
/// exclusively used to maintain compatibility with the C++ parser. No new
444+
/// uses of this should be introduced.
445+
/// - allowKeywordsAsIdentifier: If set to `true` and the parser is
446+
/// currently positioned at a keyword, consume that keyword and remap it
447+
/// to and identifier.
448+
/// - Returns: The consumed token and any unexpected tokens that were skipped.
445449
mutating func expectIdentifier(
446450
keywordRecovery: Bool = false,
447-
allowSelfOrCapitalSelfAsIdentifier: Bool = false
451+
allowSelfOrCapitalSelfAsIdentifier: Bool = false,
452+
allowKeywordsAsIdentifier: Bool = false
448453
) -> (RawUnexpectedNodesSyntax?, RawTokenSyntax) {
449454
if let identifier = self.consume(if: .identifier) {
450455
return (nil, identifier)
451456
}
457+
if allowKeywordsAsIdentifier, self.currentToken.isLexerClassifiedKeyword {
458+
return (nil, self.consumeAnyToken(remapping: .identifier))
459+
}
452460
if allowSelfOrCapitalSelfAsIdentifier, let selfOrCapitalSelf = self.consume(if: TokenSpec(.self, remapping: .identifier), TokenSpec(.Self, remapping: .identifier)) {
453461
return (nil, selfOrCapitalSelf)
454462
}

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,6 +1300,23 @@ final class DeclarationTests: XCTestCase {
13001300
)
13011301
}
13021302

1303+
func testMacroExpansionDeclarationWithKeywordName() {
1304+
assertParse(
1305+
"""
1306+
struct X {
1307+
#case
1308+
}
1309+
""",
1310+
substructure: Syntax(
1311+
MacroExpansionDeclSyntax(
1312+
poundToken: .poundToken(),
1313+
macro: .identifier("case"),
1314+
argumentList: TupleExprElementListSyntax([])
1315+
)
1316+
)
1317+
)
1318+
}
1319+
13031320
func testAttributedMacroExpansionDeclaration() {
13041321
assertParse(
13051322
"""
@@ -1452,8 +1469,8 @@ final class DeclarationTests: XCTestCase {
14521469
assertParse(
14531470
"""
14541471
struct S {
1455-
@attrib #1️⃣class
1456-
#2️⃣struct
1472+
@attrib #class
1473+
#struct
14571474
}
14581475
""",
14591476
substructure: Syntax(
@@ -1462,43 +1479,19 @@ final class DeclarationTests: XCTestCase {
14621479
decl: MacroExpansionDeclSyntax(
14631480
attributes: [.attribute(AttributeSyntax(attributeName: TypeSyntax("attrib")))],
14641481
poundToken: .poundToken(),
1465-
/*unexpectedBetweenPoundTokenAndMacro:*/ [
1466-
TokenSyntax.keyword(.class)
1467-
],
1468-
macro: .identifier("", presence: .missing),
1482+
macro: .identifier("class"),
14691483
argumentList: []
14701484
)
14711485
),
14721486
MemberDeclListItemSyntax(
14731487
decl: MacroExpansionDeclSyntax(
14741488
poundToken: .poundToken(),
1475-
/*unexpectedBetweenPoundTokenAndMacro:*/ [
1476-
TokenSyntax.keyword(.struct)
1477-
],
1478-
macro: .identifier("", presence: .missing),
1489+
macro: .identifier("struct"),
14791490
argumentList: []
14801491
)
14811492
),
14821493
])
1483-
),
1484-
diagnostics: [
1485-
DiagnosticSpec(
1486-
locationMarker: "1️⃣",
1487-
message: "keyword 'class' cannot be used as an identifier here",
1488-
fixIts: ["if this name is unavoidable, use backticks to escape it"]
1489-
),
1490-
DiagnosticSpec(
1491-
locationMarker: "2️⃣",
1492-
message: "keyword 'struct' cannot be used as an identifier here",
1493-
fixIts: ["if this name is unavoidable, use backticks to escape it"]
1494-
),
1495-
],
1496-
fixedSource: """
1497-
struct S {
1498-
@attrib #`class`
1499-
#`struct`
1500-
}
1501-
"""
1494+
)
15021495
)
15031496
}
15041497

Tests/SwiftParserTest/ExpressionTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,19 @@ final class ExpressionTests: XCTestCase {
948948
)
949949
}
950950

951+
func testMacroExpansionExpressionWithKeywordName() {
952+
assertParse(
953+
"#case",
954+
substructure: Syntax(
955+
MacroExpansionExprSyntax(
956+
poundToken: .poundToken(),
957+
macro: .identifier("case"),
958+
argumentList: TupleExprElementListSyntax([])
959+
)
960+
)
961+
)
962+
}
963+
951964
func testNewlineInInterpolationOfSingleLineString() {
952965
assertParse(
953966
#"""

0 commit comments

Comments
 (0)