Skip to content

Commit e54d492

Browse files
committed
Allow keywords after # in freestanding macro expansions
There is no reason why we shouldn’t allow keywords here. I also thought about allowing keywords after `@` but things become tricky here for two reasons: - In the parser, we parse a type after the `@`, which could start with a keyword itself (e.g. `any`). If we want to keep the parser logic to parse a type after `@` (which I think we should), then it becomes unclear what `@any T` should parse as. - We allow a space between `@` and the type name. This makes it very hard for recovery to tell whether `@ struct` refers to an attribute with name `struct` or if the user forgot to write the attribute name after `@`. Since almost all keywords are lowercase and attached member macros are usually spelled with an uppercase name, there are a lot fewer chances for clashes here, so I don’t think it’s worth allowing keywords after `@`. swiftlang/swift#66444 rdar://110472060
1 parent d79b876 commit e54d492

File tree

5 files changed

+52
-14
lines changed

5 files changed

+52
-14
lines changed

Sources/SwiftParser/Declarations.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2107,7 +2107,7 @@ extension Parser {
21072107
var unexpectedBeforeMacro: RawUnexpectedNodesSyntax?
21082108
var macro: RawTokenSyntax
21092109
if !self.currentToken.isAtStartOfLine {
2110-
(unexpectedBeforeMacro, macro) = self.expectIdentifier(keywordRecovery: true)
2110+
(unexpectedBeforeMacro, macro) = self.expectIdentifier(allowKeywordsAsIdentifier: true)
21112111
if macro.leadingTriviaByteLength != 0 {
21122112
unexpectedBeforeMacro = RawUnexpectedNodesSyntax(combining: unexpectedBeforeMacro, macro, arena: self.arena)
21132113
pound = self.missingToken(.identifier, text: macro.tokenText)

Sources/SwiftParser/Expressions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1420,7 +1420,7 @@ extension Parser {
14201420
var unexpectedBeforeMacro: RawUnexpectedNodesSyntax?
14211421
var macro: RawTokenSyntax
14221422
if !self.currentToken.isAtStartOfLine {
1423-
(unexpectedBeforeMacro, macro) = self.expectIdentifier(keywordRecovery: true)
1423+
(unexpectedBeforeMacro, macro) = self.expectIdentifier(allowKeywordsAsIdentifier: true)
14241424
if macro.leadingTriviaByteLength != 0 {
14251425
// If there're whitespaces after '#' diagnose.
14261426
unexpectedBeforeMacro = RawUnexpectedNodesSyntax(combining: unexpectedBeforeMacro, macro, arena: self.arena)

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,
453461
let selfOrCapitalSelf = self.consume(if: TokenSpec(.self, remapping: .identifier), TokenSpec(.Self, remapping: .identifier))
454462
{

Tests/SwiftParserTest/DeclarationTests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,6 +1653,23 @@ final class DeclarationTests: XCTestCase {
16531653
)
16541654
}
16551655

1656+
func testMacroExpansionDeclarationWithKeywordName() {
1657+
assertParse(
1658+
"""
1659+
struct X {
1660+
#case
1661+
}
1662+
""",
1663+
substructure: Syntax(
1664+
MacroExpansionDeclSyntax(
1665+
poundToken: .poundToken(),
1666+
macro: .identifier("case"),
1667+
argumentList: TupleExprElementListSyntax([])
1668+
)
1669+
)
1670+
)
1671+
}
1672+
16561673
func testAttributedMacroExpansionDeclaration() {
16571674
assertParse(
16581675
"""

Tests/SwiftParserTest/ExpressionTests.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,19 @@ final class ExpressionTests: XCTestCase {
11511151
)
11521152
}
11531153

1154+
func testMacroExpansionExpressionWithKeywordName() {
1155+
assertParse(
1156+
"#case",
1157+
substructure: Syntax(
1158+
MacroExpansionExprSyntax(
1159+
poundToken: .poundToken(),
1160+
macro: .identifier("case"),
1161+
argumentList: TupleExprElementListSyntax([])
1162+
)
1163+
)
1164+
)
1165+
}
1166+
11541167
func testNewlineInInterpolationOfSingleLineString() {
11551168
assertParse(
11561169
#"""

0 commit comments

Comments
 (0)