Skip to content

Commit b7b047f

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 b7b047f

File tree

5 files changed

+58
-48
lines changed

5 files changed

+58
-48
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: 23 additions & 34 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
"""
@@ -1806,60 +1823,32 @@ final class DeclarationTests: XCTestCase {
18061823
assertParse(
18071824
"""
18081825
struct S {
1809-
@attrib #1️⃣class
1826+
@attrib #class
18101827
}
18111828
""",
18121829
substructure: Syntax(
18131830
MacroExpansionDeclSyntax(
18141831
attributes: [.attribute(AttributeSyntax(attributeName: TypeSyntax("attrib")))],
18151832
poundToken: .poundToken(),
1816-
/*unexpectedBetweenPoundTokenAndMacro:*/ [
1817-
TokenSyntax.keyword(.class)
1818-
],
1819-
macro: .identifier("", presence: .missing),
1833+
macro: .identifier("class"),
18201834
argumentList: []
18211835
)
1822-
),
1823-
diagnostics: [
1824-
DiagnosticSpec(
1825-
message: "keyword 'class' cannot be used as an identifier here",
1826-
fixIts: ["if this name is unavoidable, use backticks to escape it"]
1827-
)
1828-
],
1829-
fixedSource: """
1830-
struct S {
1831-
@attrib #`class`
1832-
}
1833-
"""
1836+
)
18341837
)
18351838

18361839
assertParse(
18371840
"""
18381841
struct S {
1839-
#1️⃣struct
1842+
#struct
18401843
}
18411844
""",
18421845
substructure: Syntax(
18431846
MacroExpansionDeclSyntax(
18441847
poundToken: .poundToken(),
1845-
/*unexpectedBetweenPoundTokenAndMacro:*/ [
1846-
TokenSyntax.keyword(.struct)
1847-
],
1848-
macro: .identifier("", presence: .missing),
1848+
macro: .identifier("struct"),
18491849
argumentList: []
18501850
)
1851-
),
1852-
diagnostics: [
1853-
DiagnosticSpec(
1854-
message: "keyword 'struct' cannot be used as an identifier here",
1855-
fixIts: ["if this name is unavoidable, use backticks to escape it"]
1856-
)
1857-
],
1858-
fixedSource: """
1859-
struct S {
1860-
#`struct`
1861-
}
1862-
"""
1851+
)
18631852
)
18641853
}
18651854

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)