Skip to content

Commit 9d19cc8

Browse files
committed
[Macros] Introduce (Freestanding)DeclarationMacro and implement expansion
Freestanding declaration macros are spelled in the same manner as expression macros, with a leading `#`. Introduce a protocol with their expansion operation and implement expansion semantics in the macro system for testing purposes.
1 parent 59e6662 commit 9d19cc8

File tree

6 files changed

+187
-43
lines changed

6 files changed

+187
-43
lines changed

Sources/_SwiftSyntaxMacros/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors
88

99
add_swift_host_library(_SwiftSyntaxMacros
10+
DeclarationMacro.swift
1011
ExpressionMacro.swift
12+
FreestandingDeclarationMacro.swift
1113
Macro.swift
1214
MacroExpansionContext.swift
1315
MacroSystem.swift
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
3+
// Licensed under Apache License v2.0 with Runtime Library Exception
4+
//
5+
// See https://swift.org/LICENSE.txt for license information
6+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
import SwiftSyntax
11+
import SwiftParser
12+
import SwiftDiagnostics
13+
14+
/// Describes a macro that forms declarations.
15+
public protocol DeclarationMacro: Macro {
16+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
3+
// Licensed under Apache License v2.0 with Runtime Library Exception
4+
//
5+
// See https://swift.org/LICENSE.txt for license information
6+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
7+
//
8+
//===----------------------------------------------------------------------===//
9+
10+
import SwiftSyntax
11+
import SwiftParser
12+
import SwiftDiagnostics
13+
14+
/// Describes a macro that forms declarations.
15+
public protocol FreestandingDeclarationMacro: DeclarationMacro {
16+
/// Expand a macro described by the given freestanding macro expansion
17+
/// declaration within the given context to produce a set of declarations.
18+
static func expansion(
19+
of node: MacroExpansionDeclSyntax, in context: inout MacroExpansionContext
20+
) throws -> [DeclSyntax]
21+
}

Sources/_SwiftSyntaxMacros/MacroSystem.swift

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,18 +86,75 @@ class MacroApplication: SyntaxRewriter {
8686
override func visit(_ node: CodeBlockItemListSyntax) -> CodeBlockItemListSyntax {
8787
var newItems: [CodeBlockItemSyntax] = []
8888
for item in node {
89+
// Expand declaration macros that were parsed as macro expansion
90+
// expressions in this context.
91+
if case let .expr(exprItem) = item.item,
92+
let exprExpansion = exprItem.as(MacroExpansionExprSyntax.self),
93+
let macro = macroSystem.macros[exprExpansion.macro.text],
94+
let freestandingMacro = macro as? FreestandingDeclarationMacro.Type {
95+
do {
96+
let expandedDecls = try freestandingMacro.expansion(
97+
of: exprExpansion.asMacroExpansionDecl(), in: &context
98+
)
99+
100+
newItems.append(contentsOf: expandedDecls.map { decl in
101+
CodeBlockItemSyntax(item: .decl(decl))
102+
})
103+
} catch {
104+
// Record the error
105+
context.diagnose(
106+
Diagnostic(
107+
node: Syntax(node),
108+
message: ThrownErrorDiagnostic(message: String(describing: error))
109+
)
110+
)
111+
}
112+
113+
continue
114+
}
115+
89116
// Recurse on the child node.
90117
let newItem = visit(item.item)
118+
newItems.append(item.withItem(newItem))
119+
}
120+
121+
return CodeBlockItemListSyntax(newItems)
122+
}
91123

92-
// Flatten if we get code block item list syntax back.
93-
if let itemList = newItem.as(CodeBlockItemListSyntax.self) {
94-
newItems.append(contentsOf: itemList)
95-
} else {
96-
newItems.append(item.withItem(newItem))
124+
override func visit(_ node: MemberDeclListSyntax) -> MemberDeclListSyntax {
125+
var newItems: [MemberDeclListItemSyntax] = []
126+
for item in node {
127+
// Expand declaration macros, which produce zero or more declarations.
128+
if let declExpansion = item.decl.as(MacroExpansionDeclSyntax.self),
129+
let macro = macroSystem.macros[declExpansion.macro.text],
130+
let freestandingMacro = macro as? FreestandingDeclarationMacro.Type {
131+
do {
132+
let expandedDecls = try freestandingMacro.expansion(
133+
of: declExpansion, in: &context
134+
)
135+
136+
newItems.append(contentsOf: expandedDecls.map { decl in
137+
MemberDeclListItemSyntax(decl: decl)
138+
})
139+
} catch {
140+
// Record the error
141+
context.diagnose(
142+
Diagnostic(
143+
node: Syntax(node),
144+
message: ThrownErrorDiagnostic(message: String(describing: error))
145+
)
146+
)
147+
}
148+
149+
continue
97150
}
151+
152+
// Recurse on the child node.
153+
let newDecl = visit(item.decl)
154+
newItems.append(item.withDecl(newDecl))
98155
}
99156

100-
return CodeBlockItemListSyntax(newItems)
157+
return .init(newItems)
101158
}
102159
}
103160

Sources/_SwiftSyntaxMacros/Syntax+MacroEvaluation.swift

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,29 @@ struct ThrownErrorDiagnostic: DiagnosticMessage {
2525
}
2626

2727
extension MacroExpansionExprSyntax {
28+
/// Macro expansion declarations are parsed in some positions where an
29+
/// expression is also warranted, so
30+
func asMacroExpansionDecl() -> MacroExpansionDeclSyntax {
31+
MacroExpansionDeclSyntax(
32+
unexpectedBeforePoundToken,
33+
poundToken: poundToken,
34+
unexpectedBetweenPoundTokenAndMacro,
35+
macro: macro,
36+
genericArguments: genericArguments,
37+
unexpectedBetweenGenericArgumentsAndLeftParen,
38+
leftParen: leftParen,
39+
unexpectedBetweenLeftParenAndArgumentList,
40+
argumentList: argumentList,
41+
unexpectedBetweenArgumentListAndRightParen,
42+
rightParen: rightParen,
43+
unexpectedBetweenRightParenAndTrailingClosure,
44+
trailingClosure: trailingClosure,
45+
unexpectedBetweenTrailingClosureAndAdditionalTrailingClosures,
46+
additionalTrailingClosures: additionalTrailingClosures,
47+
unexpectedAfterAdditionalTrailingClosures
48+
)
49+
}
50+
2851
/// Evaluate the given macro for this syntax node, producing the expanded
2952
/// result and (possibly) some diagnostics.
3053
func evaluateMacro(
@@ -53,29 +76,6 @@ extension MacroExpansionExprSyntax {
5376
}
5477

5578
extension MacroExpansionDeclSyntax {
56-
/// Macro expansion declarations are parsed in some positions where an
57-
/// expression is also warranted, so
58-
private func asMacroExpansionExpr() -> MacroExpansionExprSyntax {
59-
MacroExpansionExprSyntax(
60-
unexpectedBeforePoundToken,
61-
poundToken: poundToken,
62-
unexpectedBetweenPoundTokenAndMacro,
63-
macro: macro,
64-
genericArguments: genericArguments,
65-
unexpectedBetweenGenericArgumentsAndLeftParen,
66-
leftParen: leftParen,
67-
unexpectedBetweenLeftParenAndArgumentList,
68-
argumentList: argumentList,
69-
unexpectedBetweenArgumentListAndRightParen,
70-
rightParen: rightParen,
71-
unexpectedBetweenRightParenAndTrailingClosure,
72-
trailingClosure: trailingClosure,
73-
unexpectedBetweenTrailingClosureAndAdditionalTrailingClosures,
74-
additionalTrailingClosures: additionalTrailingClosures,
75-
unexpectedAfterAdditionalTrailingClosures
76-
)
77-
}
78-
7979
/// Evaluate the given macro for this syntax node, producing the expanded
8080
/// result and (possibly) some diagnostics.
8181
func evaluateMacro(
@@ -84,10 +84,7 @@ extension MacroExpansionDeclSyntax {
8484
) -> Syntax {
8585
// TODO: declaration/statement macros
8686

87-
// Fall back to evaluating as an expression macro.
88-
return Syntax(
89-
asMacroExpansionExpr().evaluateMacro(macro, in: &context)
90-
)
87+
return Syntax(self)
9188
}
9289
}
9390

Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,12 @@ extension SimpleDiagnosticMessage: FixItMessage {
154154
var fixItID: MessageID { diagnosticID }
155155
}
156156

157-
public struct ErrorMacro: ExpressionMacro {
157+
public struct ErrorMacro: FreestandingDeclarationMacro {
158158
public static func expansion(
159-
of macro: MacroExpansionExprSyntax,
159+
of node: MacroExpansionDeclSyntax,
160160
in context: inout MacroExpansionContext
161-
) throws -> ExprSyntax {
162-
guard let firstElement = macro.argumentList.first,
161+
) throws -> [DeclSyntax] {
162+
guard let firstElement = node.argumentList.first,
163163
let stringLiteral = firstElement.expression
164164
.as(StringLiteralExprSyntax.self),
165165
stringLiteral.segments.count == 1,
@@ -170,7 +170,7 @@ public struct ErrorMacro: ExpressionMacro {
170170

171171
context.diagnose(
172172
Diagnostic(
173-
node: Syntax(macro),
173+
node: Syntax(node),
174174
message: SimpleDiagnosticMessage(
175175
message: messageString.content.description,
176176
diagnosticID: MessageID(domain: "test", id: "error"),
@@ -179,7 +179,31 @@ public struct ErrorMacro: ExpressionMacro {
179179
)
180180
)
181181

182-
return "()"
182+
return []
183+
}
184+
}
185+
186+
struct DefineBitwidthNumberedStructsMacro: FreestandingDeclarationMacro {
187+
static func expansion(
188+
of node: MacroExpansionDeclSyntax,
189+
in context: inout MacroExpansionContext
190+
) throws -> [DeclSyntax] {
191+
guard let firstElement = node.argumentList.first,
192+
let stringLiteral = firstElement.expression
193+
.as(StringLiteralExprSyntax.self),
194+
stringLiteral.segments.count == 1,
195+
case let .stringSegment(prefix) = stringLiteral.segments[0]
196+
else {
197+
throw CustomError.message(
198+
"#bitwidthNumberedStructs macro requires a string literal")
199+
}
200+
201+
return [8, 16, 32, 64].map { bitwidth in
202+
"""
203+
204+
struct \(raw: prefix)\(raw: String(bitwidth)) { }
205+
"""
206+
}
183207
}
184208
}
185209

@@ -243,6 +267,7 @@ public let testMacros: [String: Macro.Type] = [
243267
"imageLiteral": ImageLiteralMacro.self,
244268
"stringify": StringifyMacro.self,
245269
"myError": ErrorMacro.self,
270+
"bitwidthNumberedStructs": DefineBitwidthNumberedStructsMacro.self,
246271
]
247272

248273
final class MacroSystemTests: XCTestCase {
@@ -320,18 +345,44 @@ final class MacroSystemTests: XCTestCase {
320345
AssertMacroExpansion(
321346
macros: testMacros,
322347
"""
323-
_ = #myError("please don't do that")
324-
_ = #myError(bad)
348+
#myError("please don't do that")
349+
struct X {
350+
func f() { }
351+
#myError(bad)
352+
func g() {
353+
#myError("worse")
354+
}
355+
}
325356
""",
326357
"""
327-
_ = ()
328-
_ = #myError(bad)
358+
359+
struct X {
360+
func f() { }
361+
func g() {
362+
}
363+
}
329364
""",
330365
diagnosticStrings: [
331366
"please don't do that",
332367
"#error macro requires a string literal",
368+
"worse",
333369
]
334370
)
335371
}
336372

373+
func testBitwidthNumberedStructsExpansion() {
374+
AssertMacroExpansion(
375+
macros: testMacros,
376+
"""
377+
#bitwidthNumberedStructs("MyInt")
378+
""",
379+
"""
380+
381+
struct MyInt8 { }
382+
struct MyInt16 { }
383+
struct MyInt32 { }
384+
struct MyInt64 { }
385+
"""
386+
)
387+
}
337388
}

0 commit comments

Comments
 (0)