Skip to content

[Macros] Freestanding macros expand to CodeBlockItemList; parse top-level macro expansion decls #1268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions Sources/SwiftParser/Declarations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,16 @@ extension TokenConsumer {
mutating func atStartOfDeclaration(
isAtTopLevel: Bool = false,
allowInitDecl: Bool = true,
allowRecovery: Bool = false
allowRecovery: Bool = false,
preferPoundAsExpression: Bool = false
) -> Bool {
if self.at(anyIn: PoundDeclarationStart.self) != nil {
// If we are in a context where we prefer # to be an expression,
// treat it as one if it's not at the start of the line.
if preferPoundAsExpression && self.at(.pound) && !self.currentToken.isAtStartOfLine {
return false
}

return true
}

Expand Down Expand Up @@ -192,14 +199,12 @@ extension Parser {
return RawDeclSyntax(directive)
case (.poundWarningKeyword, _)?, (.poundErrorKeyword, _)?:
return self.parsePoundDiagnosticDeclaration()
case nil:
break
}

if (self.at(.pound)) {
case (.pound, _)?:
// FIXME: If we can have attributes for macro expansions, handle this
// via DeclarationStart.
return RawDeclSyntax(self.parseMacroExpansionDeclaration())
case nil:
break
}

let attrs = DeclAttributes(
Expand Down
3 changes: 3 additions & 0 deletions Sources/SwiftParser/RawTokenKindSubset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -513,12 +513,14 @@ enum PoundDeclarationStart: RawTokenKindSubset {
case poundIfKeyword
case poundWarningKeyword
case poundErrorKeyword
case pound

init?(lexeme: Lexer.Lexeme) {
switch lexeme.rawTokenKind {
case .poundIfKeyword: self = .poundIfKeyword
case .poundWarningKeyword: self = .poundWarningKeyword
case .poundErrorKeyword: self = .poundErrorKeyword
case .pound: self = .pound
default: return nil
}
}
Expand All @@ -528,6 +530,7 @@ enum PoundDeclarationStart: RawTokenKindSubset {
case .poundIfKeyword: return .poundIfKeyword
case .poundWarningKeyword: return .poundWarningKeyword
case .poundErrorKeyword: return .poundErrorKeyword
case .pound: return .pound
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion Sources/SwiftParser/Statements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,8 @@ extension Parser {
.poundIfKeyword, .poundErrorKeyword, .poundWarningKeyword,
.poundEndifKeyword, .poundElseKeyword, .poundElseifKeyword,
])
&& !self.atStartOfStatement() && !self.atStartOfDeclaration()
&& !self.atStartOfStatement() &&
!self.atStartOfDeclaration(preferPoundAsExpression: true)
{
let parsedExpr = self.parseExpression()
if hasMisplacedTry && !parsedExpr.is(RawTryExprSyntax.self) {
Expand Down
2 changes: 1 addition & 1 deletion Sources/_SwiftSyntaxMacros/CodeItemMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ public protocol CodeItemMacro: FreestandingMacro {
static func expansion(
of node: MacroExpansionDeclSyntax,
in context: inout MacroExpansionContext
) throws -> [CodeBlockItemSyntax]
) throws -> CodeBlockItemListSyntax
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I discussed this with @DougGregor yesterday. If you are intending to merge these items with other items, this should return an array IMO because arrays are easier to work with (you can append, insert, delete, …). IMO you should only create a CodeBlockItemListSyntax once you think the item list is complete.

(similar in DeclarationMacros.swift)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! In that case this PR is no longer necessary. DeclarationMacro still returns decls, ExpressionMacro still returns exprs, and CodeItemMacro returns [CodeBlockItemSyntax]

}
2 changes: 1 addition & 1 deletion Sources/_SwiftSyntaxMacros/DeclarationMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public protocol DeclarationMacro: FreestandingMacro {
static func expansion(
of node: MacroExpansionDeclSyntax,
in context: inout MacroExpansionContext
) throws -> [DeclSyntax]
) throws -> CodeBlockItemListSyntax
}

@available(*, deprecated, renamed: "DeclarationMacro")
Expand Down
44 changes: 24 additions & 20 deletions Sources/_SwiftSyntaxMacros/MacroSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,22 +127,22 @@ class MacroApplication: SyntaxRewriter {
for item in node {
// Expand declaration macros that were parsed as macro expansion
// expressions in this context.
if case let .expr(exprItem) = item.item,
let exprExpansion = exprItem.as(MacroExpansionExprSyntax.self),
let macro = macroSystem.macros[exprExpansion.macro.text],
let freestandingMacro = macro as? DeclarationMacro.Type
if case let .decl(declItem) = item.item,
let declExpansion = declItem.as(MacroExpansionDeclSyntax.self),
let macro = macroSystem.macros[declExpansion.macro.text]
{
do {
let expandedDecls = try freestandingMacro.expansion(
of: exprExpansion.asMacroExpansionDecl(),
in: &context
)

newItems.append(
contentsOf: expandedDecls.map { decl in
CodeBlockItemSyntax(item: .decl(decl))
}
)
if let macro = macro as? DeclarationMacro.Type {
let expandedItemList = try macro.expansion(
of: declExpansion, in: &context
)
newItems.append(contentsOf: expandedItemList)
} else if let macro = macro as? ExpressionMacro.Type {
let expandedExpr = try macro.expansion(
of: declExpansion.asMacroExpansionExpr(), in: &context
)
newItems.append(CodeBlockItemSyntax(item: .init(expandedExpr)))
}
} catch {
// Record the error
context.diagnose(
Expand Down Expand Up @@ -183,16 +183,20 @@ class MacroApplication: SyntaxRewriter {
let freestandingMacro = macro as? DeclarationMacro.Type
{
do {
let expandedDecls = try freestandingMacro.expansion(
let expandedList = try freestandingMacro.expansion(
of: declExpansion,
in: &context
)

newItems.append(
contentsOf: expandedDecls.map { decl in
MemberDeclListItemSyntax(decl: decl)
}
)
newItems.append(contentsOf: expandedList.compactMap { item -> MemberDeclListItemSyntax? in
guard let decl = item.item.as(DeclSyntax.self) else { return nil }
return MemberDeclListItemSyntax(
leadingTrivia: item.leadingTrivia,
item.unexpectedBeforeItem,
decl: decl,
semicolon: item.semicolon,
trailingTrivia: item.trailingTrivia)
})
} catch {
// Record the error
context.diagnose(
Expand Down
8 changes: 5 additions & 3 deletions Sources/_SwiftSyntaxMacros/Syntax+MacroEvaluation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ struct ThrownErrorDiagnostic: DiagnosticMessage {
}
}

extension MacroExpansionExprSyntax {
extension MacroExpansionDeclSyntax {
/// Macro expansion declarations are parsed in some positions where an
/// expression is also warranted, so
func asMacroExpansionDecl() -> MacroExpansionDeclSyntax {
MacroExpansionDeclSyntax(
public func asMacroExpansionExpr() -> MacroExpansionExprSyntax {
MacroExpansionExprSyntax(
unexpectedBeforePoundToken,
poundToken: poundToken,
unexpectedBetweenPoundTokenAndMacro,
Expand All @@ -47,7 +47,9 @@ extension MacroExpansionExprSyntax {
unexpectedAfterAdditionalTrailingClosures
)
}
}

extension MacroExpansionExprSyntax {
/// Evaluate the given macro for this syntax node, producing the expanded
/// result and (possibly) some diagnostics.
func evaluateMacro(
Expand Down
12 changes: 12 additions & 0 deletions Tests/SwiftParserTest/DeclarationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,18 @@ final class DeclarationTests: XCTestCase {
}
"""
)
AssertParse(
"""
#expand
""",
substructure: Syntax(
SourceFileSyntax(
CodeBlockItemListSyntax {
MacroExpansionDeclSyntax(macro: "expand")
}
)
)
)
}

func testVariableDeclWithGetSetButNoBrace() {
Expand Down
2 changes: 1 addition & 1 deletion Tests/SwiftParserTest/ExpressionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ final class ExpressionTests: XCTestCase {
"#keyPath((b:1️⃣)2️⃣",
diagnostics: [
DiagnosticSpec(locationMarker: "1️⃣", message: "expected value in tuple"),
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ')' to end pound literal expression"),
DiagnosticSpec(locationMarker: "2️⃣", message: "expected ')' to end pound literal declaration"),
]
)
}
Expand Down
11 changes: 8 additions & 3 deletions Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public struct ErrorMacro: DeclarationMacro {
public static func expansion(
of node: MacroExpansionDeclSyntax,
in context: inout MacroExpansionContext
) throws -> [DeclSyntax] {
) throws -> CodeBlockItemListSyntax {
guard let firstElement = node.argumentList.first,
let stringLiteral = firstElement.expression
.as(StringLiteralExprSyntax.self),
Expand Down Expand Up @@ -187,7 +187,7 @@ struct DefineBitwidthNumberedStructsMacro: DeclarationMacro {
static func expansion(
of node: MacroExpansionDeclSyntax,
in context: inout MacroExpansionContext
) throws -> [DeclSyntax] {
) throws -> CodeBlockItemListSyntax {
guard let firstElement = node.argumentList.first,
let stringLiteral = firstElement.expression
.as(StringLiteralExprSyntax.self),
Expand All @@ -199,12 +199,17 @@ struct DefineBitwidthNumberedStructsMacro: DeclarationMacro {
)
}

return [8, 16, 32, 64].map { bitwidth in
let decls: [DeclSyntax] = [8, 16, 32, 64].map { bitwidth in
"""

struct \(raw: prefix)\(raw: String(bitwidth)) { }
"""
}
// TODO: Interpolate to `CodeBlockItemList` directly when it supports
// string interpolation.
return CodeBlockItemListSyntax(decls.map {
CodeBlockItemSyntax(item: .init($0))
})
}
}

Expand Down