Skip to content

Commit e91bbeb

Browse files
committed
[Macros] Implement function body macros
Introduce function body macros, which are comprised of two similar macro roles: - Preamble macros introduce "preamble" code into a user-written function body, e.g., to perform tracing, logging, check additional preconditions, etc. - Body macros: introduce a function body into a function that has none.
1 parent 7617b70 commit e91bbeb

File tree

10 files changed

+303
-5
lines changed

10 files changed

+303
-5
lines changed

Sources/SwiftCompilerPluginMessageHandling/Macros.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ private extension MacroRole {
165165
case .conformance: self = .conformance
166166
case .codeItem: self = .codeItem
167167
case .extension: self = .extension
168+
case .preamble: self = .preamble
169+
case .body: self = .body
168170
}
169171
}
170172
}

Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@
123123
case conformance
124124
case codeItem
125125
case `extension`
126+
case preamble
127+
case body
126128
}
127129

128130
public struct SourceLocation: Codable {

Sources/SwiftSyntaxMacroExpansion/FunctionParameterUtils.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ extension FunctionParameterSyntax {
99
///
1010
/// The parameter names for these three parameters are `a`, `b`, and `see`,
1111
/// respectively.
12-
var parameterName: TokenSyntax? {
12+
@_spi(Testing)
13+
public var parameterName: TokenSyntax? {
1314
// If there were two names, the second is the parameter name.
1415
if let secondName {
1516
if secondName.text == "_" {

Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import SwiftSyntax
1414
@_spi(MacroExpansion) import SwiftSyntaxMacros
15+
import SwiftSyntaxBuilder // FIXME: Why is HasTrailingOptionalCodeBlock there?
1516

1617
public enum MacroRole {
1718
case expression
@@ -23,6 +24,8 @@ public enum MacroRole {
2324
case conformance
2425
case codeItem
2526
case `extension`
27+
case preamble
28+
case body
2629
}
2730

2831
extension MacroRole {
@@ -37,6 +40,8 @@ extension MacroRole {
3740
case .conformance: return "ConformanceMacro"
3841
case .codeItem: return "CodeItemMacro"
3942
case .extension: return "ExtensionMacro"
43+
case .preamble: return "PreambleMacro"
44+
case .body: return "BodyMacro"
4045
}
4146
}
4247
}
@@ -47,6 +52,7 @@ private enum MacroExpansionError: Error, CustomStringConvertible {
4752
case parentDeclGroupNil
4853
case declarationNotDeclGroup
4954
case declarationNotIdentified
55+
case declarationHasNoBody
5056
case noExtendedTypeSyntax
5157
case noFreestandingMacroRoles(Macro.Type)
5258

@@ -64,12 +70,14 @@ private enum MacroExpansionError: Error, CustomStringConvertible {
6470
case .declarationNotIdentified:
6571
return "declaration is not a 'Identified' syntax"
6672

73+
case .declarationHasNoBody:
74+
return "declaration is not a type with an optional code block"
75+
6776
case .noExtendedTypeSyntax:
6877
return "no extended type for extension macro"
6978

7079
case .noFreestandingMacroRoles(let type):
7180
return "macro implementation type '\(type)' does not conform to any freestanding macro protocol"
72-
7381
}
7482
}
7583
}
@@ -120,7 +128,7 @@ public func expandFreestandingMacro(
120128
expandedSyntax = Syntax(CodeBlockItemListSyntax(rewritten))
121129

122130
case (.accessor, _), (.memberAttribute, _), (.member, _), (.peer, _), (.conformance, _), (.extension, _), (.expression, _), (.declaration, _),
123-
(.codeItem, _):
131+
(.codeItem, _), (.preamble, _), (.body, _):
124132
throw MacroExpansionError.unmatchedMacroRole(definition, macroRole)
125133
}
126134
return expandedSyntax.formattedExpansion(definition.formatMode)
@@ -335,6 +343,38 @@ public func expandAttachedMacroWithoutCollapsing<Context: MacroExpansionContext>
335343
$0.formattedExpansion(definition.formatMode)
336344
}
337345

346+
case (let attachedMacro as PreambleMacro.Type, .preamble):
347+
guard let declToPass = Syntax(declarationNode).asProtocol(SyntaxProtocol.self) as? (DeclSyntaxProtocol & HasTrailingOptionalCodeBlock)
348+
else {
349+
// Compiler error: declaration must have a body.
350+
throw MacroExpansionError.declarationHasNoBody
351+
}
352+
353+
let preamble = try attachedMacro.expansion(
354+
of: attributeNode,
355+
providingPreambleFor: declToPass,
356+
in: context
357+
)
358+
return preamble.map {
359+
$0.formattedExpansion(definition.formatMode)
360+
}
361+
362+
case (let attachedMacro as BodyMacro.Type, .body):
363+
guard let declToPass = Syntax(declarationNode).asProtocol(SyntaxProtocol.self) as? (DeclSyntaxProtocol & HasTrailingOptionalCodeBlock)
364+
else {
365+
// Compiler error: declaration must have a body.
366+
throw MacroExpansionError.declarationHasNoBody
367+
}
368+
369+
let body = try attachedMacro.expansion(
370+
of: attributeNode,
371+
providingBodyFor: declToPass,
372+
in: context
373+
)
374+
return body.map {
375+
$0.formattedExpansion(definition.formatMode)
376+
}
377+
338378
default:
339379
throw MacroExpansionError.unmatchedMacroRole(definition, macroRole)
340380
}

Sources/SwiftSyntaxMacroExpansion/MacroSystem.swift

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import SwiftDiagnostics
1414
import SwiftSyntax
1515
@_spi(MacroExpansion) import SwiftSyntaxMacros
16+
import SwiftSyntaxBuilder // FIXME: Why is HasTrailingOptionalCodeBlock there?
1617

1718
private func expandMemberAttributeMacro(attribute: AttributeSyntax, attachedTo: DeclSyntax) -> AttributeListSyntax {
1819
fatalError("unimplemented")
@@ -137,7 +138,9 @@ class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
137138
|| macro is AccessorMacro.Type
138139
|| macro is MemberAttributeMacro.Type
139140
|| macro is ConformanceMacro.Type
140-
|| macro is ExtensionMacro.Type)
141+
|| macro is ExtensionMacro.Type
142+
|| macro is PreambleMacro.Type
143+
|| macro is BodyMacro.Type)
141144
}
142145

143146
if newAttributes.isEmpty {
@@ -382,6 +385,30 @@ class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
382385
}
383386

384387
// Subscripts
388+
389+
// Declarations with optional trailing code blocks.
390+
func visit<DeclType: DeclSyntaxProtocol & HasTrailingOptionalCodeBlock>(
391+
function: DeclType
392+
) -> DeclSyntax {
393+
let body = expandBody(decl: function).map { visit($0) }
394+
return DeclSyntax(function.with(\.body, body))
395+
}
396+
397+
override func visit(_ node: AccessorDeclSyntax) -> DeclSyntax {
398+
return visit(function: node)
399+
}
400+
401+
override func visit(_ node: DeinitializerDeclSyntax) -> DeclSyntax {
402+
return visit(function: node)
403+
}
404+
405+
override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax {
406+
return visit(function: node)
407+
}
408+
409+
override func visit(_ node: InitializerDeclSyntax) -> DeclSyntax {
410+
return visit(function: node)
411+
}
385412
}
386413

387414
extension MacroApplication {
@@ -510,6 +537,56 @@ extension MacroApplication {
510537
)
511538
}
512539

540+
private func expandPreamble(
541+
for decl: some DeclSyntaxProtocol & HasTrailingOptionalCodeBlock
542+
) -> [CodeBlockItemSyntax] {
543+
var preamble: [CodeBlockItemSyntax] = []
544+
let macroAttributes = getMacroAttributes(attachedTo: DeclSyntax(decl), ofType: PreambleMacro.Type.self)
545+
for (attribute, preambleMacro) in macroAttributes {
546+
do {
547+
let newPreamble = try preambleMacro.expansion(
548+
of: attribute,
549+
providingPreambleFor: decl,
550+
in: context
551+
)
552+
preamble.append(contentsOf: newPreamble)
553+
} catch {
554+
context.addDiagnostics(from: error, node: attribute)
555+
}
556+
}
557+
558+
return preamble
559+
}
560+
561+
private func expandBody(
562+
decl: some DeclSyntaxProtocol & HasTrailingOptionalCodeBlock
563+
) -> CodeBlockSyntax? {
564+
// Extract the current body from the declaration.
565+
guard let body = decl.body else {
566+
// When there is no body, a body macro can synthesize one.
567+
let macroAttributes = getMacroAttributes(attachedTo: DeclSyntax(decl), ofType: BodyMacro.Type.self)
568+
for (attribute, bodyMacro) in macroAttributes {
569+
do {
570+
let statements = try bodyMacro.expansion(of: attribute, providingBodyFor: decl, in: context)
571+
return CodeBlockSyntax(statements: CodeBlockItemListSyntax(statements))
572+
} catch {
573+
context.addDiagnostics(from: error, node: attribute)
574+
}
575+
}
576+
577+
return nil
578+
}
579+
580+
// Expand the preamble. If there wasn't any, we're done.
581+
let preamble = expandPreamble(for: decl)
582+
if preamble.isEmpty {
583+
return body
584+
}
585+
586+
// Add the preamble to the beginning of the body.
587+
return body.with(\.statements, CodeBlockItemListSyntax(preamble + Array(body.statements)))
588+
}
589+
513590
private func expandMemberAttribute(
514591
attribute: AttributeSyntax,
515592
macro: MemberAttributeMacro.Type,

Sources/SwiftSyntaxMacros/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
add_swift_host_library(SwiftSyntaxMacros
1010
MacroProtocols/AccessorMacro.swift
1111
MacroProtocols/AttachedMacro.swift
12+
MacroProtocols/BodyMacro.swift
1213
MacroProtocols/CodeItemMacro.swift
1314
MacroProtocols/ConformanceMacro.swift
1415
MacroProtocols/DeclarationMacro.swift
@@ -20,6 +21,7 @@ add_swift_host_library(SwiftSyntaxMacros
2021
MacroProtocols/MemberAttributeMacro.swift
2122
MacroProtocols/MemberMacro.swift
2223
MacroProtocols/PeerMacro.swift
24+
MacroProtocols/PreambleMacro.swift
2325

2426
AbstractSourceLocation.swift
2527
BasicMacroExpansionContext.swift
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 SwiftSyntaxBuilder // FIXME: Why is HasTrailingOptionalCodeBlock there?
12+
13+
/// Describes a macro that can create the body for a function that does not
14+
/// have one.
15+
public protocol BodyMacro: AttachedMacro {
16+
/// Expand a macro described by the given custom attribute and
17+
/// attached to the given declaration and evaluated within a
18+
/// particular expansion context.
19+
///
20+
/// The macro expansion can introduce a body for the given function.
21+
static func expansion(
22+
of node: AttributeSyntax,
23+
providingBodyFor declaration: some DeclSyntaxProtocol & HasTrailingOptionalCodeBlock,
24+
in context: some MacroExpansionContext
25+
) throws -> [CodeBlockItemSyntax]
26+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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 SwiftSyntaxBuilder // FIXME: Why is HasTrailingOptionalCodeBlock there?
12+
13+
/// Describes a macro that can introduce "preamble" code into an existing
14+
/// function body.
15+
public protocol PreambleMacro: AttachedMacro {
16+
/// Expand a macro described by the given custom attribute and
17+
/// attached to the given declaration and evaluated within a
18+
/// particular expansion context.
19+
///
20+
/// The macro expansion can introduce code items that form a preamble to
21+
/// the body of the given function. The code items produced by this macro
22+
/// expansion will be inserted at the beginning of the function body.
23+
static func expansion(
24+
of node: AttributeSyntax,
25+
providingPreambleFor declaration: some DeclSyntaxProtocol & HasTrailingOptionalCodeBlock,
26+
in context: some MacroExpansionContext
27+
) throws -> [CodeBlockItemSyntax]
28+
}

Sources/SwiftSyntaxMacros/MacroSystem.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import SwiftDiagnostics
1414
import SwiftSyntax
15+
import SwiftSyntaxBuilder // FIXME: Why is HasTrailingOptionalCodeBlock there?
1516

1617
/// Describes the kinds of errors that can occur within a macro system.
1718
enum MacroSystemError: Error {
@@ -116,7 +117,9 @@ class MacroApplication<Context: MacroExpansionContext>: SyntaxRewriter {
116117
|| macro is AccessorMacro.Type
117118
|| macro is MemberAttributeMacro.Type
118119
|| macro is ConformanceMacro.Type
119-
|| macro is ExtensionMacro.Type)
120+
|| macro is ExtensionMacro.Type
121+
|| macro is BodyMacro.Type
122+
|| macro is PreambleMacro.Type)
120123
}
121124

122125
if newAttributes.isEmpty {

0 commit comments

Comments
 (0)