Skip to content

Commit e91b3f9

Browse files
committed
[Macros] Introduce member attribute macros.
For each member of the declaration the macro attribute is attached to, the macro can produce a set of attributes to attach to that member.
1 parent 251492d commit e91b3f9

File tree

4 files changed

+242
-3
lines changed

4 files changed

+242
-3
lines changed

Sources/_SwiftSyntaxMacros/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ add_swift_host_library(_SwiftSyntaxMacros
1313
Macro.swift
1414
MacroExpansionContext.swift
1515
MacroSystem.swift
16+
MemberAttributeMacro.swift
1617
MemberDeclarationMacro.swift
1718
PeerDeclarationMacro.swift
1819
Syntax+MacroEvaluation.swift

Sources/_SwiftSyntaxMacros/MacroSystem.swift

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ class MacroApplication: SyntaxRewriter {
6565
var context: MacroExpansionContext
6666
var skipNodes: Set<Syntax> = []
6767

68+
/// A stack of member attribute macos to expand when iterating over a `MemberDeclListSyntax`.
69+
var memberAttributeMacros: [([(CustomAttributeSyntax, MemberAttributeMacro.Type)], DeclSyntax)] = []
70+
6871
init(
6972
macroSystem: MacroSystem,
7073
context: MacroExpansionContext
@@ -106,7 +109,7 @@ class MacroApplication: SyntaxRewriter {
106109
return true
107110
}
108111

109-
return !(macro is PeerDeclarationMacro.Type || macro is MemberDeclarationMacro.Type)
112+
return !(macro is PeerDeclarationMacro.Type || macro is MemberDeclarationMacro.Type || macro is MemberAttributeMacro.Type)
110113
}
111114

112115
if newAttributes.isEmpty {
@@ -203,9 +206,21 @@ class MacroApplication: SyntaxRewriter {
203206
continue
204207
}
205208

209+
// Expand member attribute members attached to the declaration context.
210+
let attributedMember: MemberDeclListSyntax.Element
211+
if let (macroAttributes, decl) = memberAttributeMacros.last {
212+
attributedMember = expandAttributes(
213+
for: macroAttributes,
214+
attachedTo: decl,
215+
annotating: item
216+
)
217+
} else {
218+
attributedMember = item
219+
}
220+
206221
// Recurse on the child node.
207-
let newDecl = visit(item.decl)
208-
newItems.append(item.withDecl(newDecl))
222+
let newDecl = visit(attributedMember.decl)
223+
newItems.append(attributedMember.withDecl(newDecl))
209224

210225
// Expand any peer declarations triggered by macros used as attributes.
211226
let peers = expandPeers(of: item.decl)
@@ -222,6 +237,14 @@ class MacroApplication: SyntaxRewriter {
222237
func visit<DeclType: DeclGroupSyntax & DeclSyntaxProtocol>(
223238
declGroup: DeclType
224239
) -> DeclSyntax {
240+
memberAttributeMacros.append(
241+
(
242+
getMacroAttributes(attachedTo: DeclSyntax(declGroup), ofType: MemberAttributeMacro.Type.self),
243+
DeclSyntax(declGroup)
244+
)
245+
)
246+
defer { memberAttributeMacros.removeLast() }
247+
225248
// Expand any attached member macros.
226249
let expandedDeclGroup = expandMembers(of: declGroup)
227250

@@ -338,6 +361,45 @@ extension MacroApplication {
338361
}
339362
)
340363
}
364+
365+
private func expandAttributes(
366+
for macroAttributes: [(CustomAttributeSyntax, MemberAttributeMacro.Type)],
367+
attachedTo decl: DeclSyntax,
368+
annotating member: MemberDeclListSyntax.Element
369+
) -> MemberDeclListSyntax.Element {
370+
guard let attributedDecl = member.decl.asProtocol(AttributedSyntax.self) else {
371+
return member
372+
}
373+
374+
var attributes: [CustomAttributeSyntax] = []
375+
for (attribute, attributeMacro) in macroAttributes {
376+
do {
377+
try attributes.append(
378+
contentsOf: attributeMacro.expansion(
379+
of: attribute,
380+
attachedTo: DeclSyntax(decl),
381+
annotating: member.decl,
382+
in: &context
383+
)
384+
)
385+
} catch {
386+
// Record the error
387+
context.diagnose(
388+
Diagnostic(
389+
node: Syntax(attribute),
390+
message: ThrownErrorDiagnostic(message: String(describing: error))
391+
)
392+
)
393+
}
394+
}
395+
396+
let newAttributes = attributes.reduce(attributedDecl.attributes ?? .init([])) {
397+
$0.appending(AttributeListSyntax.Element($1))
398+
}
399+
400+
let newDecl = attributedDecl.withAttributes(newAttributes).as(DeclSyntax.self)!
401+
return member.withDecl(newDecl)
402+
}
341403
}
342404

343405
extension SyntaxProtocol {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
15+
/// Describes a macro that can add attributes to the members inside the
16+
/// declaration it's attached to.
17+
public protocol MemberAttributeMacro: DeclarationMacro {
18+
/// Expand an attached declaration macro to produce an attribute list for
19+
/// a given member.
20+
///
21+
/// - Parameters:
22+
/// - node: The custom attribute describing the attached macro.
23+
/// - declaration: The declaration the macro attribute is attached to.
24+
/// - member: The member delcaration to attach the resulting attributes to.
25+
/// - context: The context in which to perform the macro expansion.
26+
///
27+
/// - Returns: the set of attributes to apply to the given member.
28+
static func expansion(
29+
of node: CustomAttributeSyntax,
30+
attachedTo declaration: DeclSyntax,
31+
annotating member: DeclSyntax,
32+
in context: inout MacroExpansionContext
33+
) throws -> [CustomAttributeSyntax]
34+
}

Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,70 @@ public struct AddBackingStorage: MemberDeclarationMacro {
334334
}
335335
}
336336

337+
public struct WrapAllProperties: MemberAttributeMacro {
338+
public static func expansion(
339+
of node: CustomAttributeSyntax,
340+
attachedTo decl: DeclSyntax,
341+
annotating member: DeclSyntax,
342+
in context: inout MacroExpansionContext
343+
) throws -> [CustomAttributeSyntax] {
344+
guard member.is(VariableDeclSyntax.self) else {
345+
return []
346+
}
347+
348+
return [
349+
CustomAttributeSyntax(
350+
attributeName: SimpleTypeIdentifierSyntax(
351+
name: .identifier("Wrapper")
352+
)
353+
)
354+
.withLeadingTrivia([.newlines(1), .spaces(2)])
355+
]
356+
}
357+
}
358+
359+
public struct WrapStoredProperties: MemberAttributeMacro {
360+
public static func expansion(
361+
of node: CustomAttributeSyntax,
362+
attachedTo decl: DeclSyntax,
363+
annotating member: DeclSyntax,
364+
in context: inout MacroExpansionContext
365+
) throws -> [CustomAttributeSyntax] {
366+
guard let property = member.as(VariableDeclSyntax.self),
367+
property.bindings.count == 1
368+
else {
369+
return []
370+
}
371+
372+
let binding = property.bindings.first!
373+
switch binding.accessor {
374+
case .none:
375+
break
376+
case .accessors(let node):
377+
for accessor in node.accessors {
378+
switch accessor.accessorKind.tokenKind {
379+
case .contextualKeyword(.get), .contextualKeyword(.set):
380+
return []
381+
default:
382+
break
383+
}
384+
}
385+
break
386+
case .getter:
387+
return []
388+
}
389+
390+
return [
391+
CustomAttributeSyntax(
392+
attributeName: SimpleTypeIdentifierSyntax(
393+
name: .identifier("Wrapper")
394+
)
395+
)
396+
.withLeadingTrivia([.newlines(1), .spaces(2)])
397+
]
398+
}
399+
}
400+
337401
// MARK: Assertion helper functions
338402

339403
/// Assert that expanding the given macros in the original source produces
@@ -397,6 +461,8 @@ public let testMacros: [String: Macro.Type] = [
397461
"bitwidthNumberedStructs": DefineBitwidthNumberedStructsMacro.self,
398462
"addCompletionHandler": AddCompletionHandler.self,
399463
"addBackingStorage": AddBackingStorage.self,
464+
"wrapAllProperties": WrapAllProperties.self,
465+
"wrapStoredProperties": WrapStoredProperties.self,
400466
]
401467

402468
final class MacroSystemTests: XCTestCase {
@@ -553,4 +619,80 @@ final class MacroSystemTests: XCTestCase {
553619
"""
554620
)
555621
}
622+
623+
func testWrapAllProperties() {
624+
AssertMacroExpansion(
625+
macros: testMacros,
626+
"""
627+
@wrapAllProperties
628+
struct Point {
629+
var x: Int
630+
var y: Int
631+
var description: String { "" }
632+
var computed: Int {
633+
get { 0 }
634+
set {}
635+
}
636+
637+
func test() {}
638+
}
639+
""",
640+
"""
641+
642+
struct Point {
643+
@Wrapper
644+
var x: Int
645+
@Wrapper
646+
var y: Int
647+
@Wrapper
648+
var description: String { "" }
649+
@Wrapper
650+
var computed: Int {
651+
get { 0 }
652+
set {}
653+
}
654+
655+
func test() {}
656+
}
657+
"""
658+
)
659+
660+
AssertMacroExpansion(
661+
macros: testMacros,
662+
"""
663+
@wrapStoredProperties
664+
struct Point {
665+
var x: Int
666+
var y: Int
667+
668+
var description: String { "" }
669+
670+
var computed: Int {
671+
get { 0 }
672+
set {}
673+
}
674+
675+
func test() {}
676+
}
677+
""",
678+
"""
679+
680+
struct Point {
681+
@Wrapper
682+
var x: Int
683+
@Wrapper
684+
var y: Int
685+
686+
var description: String { "" }
687+
688+
var computed: Int {
689+
get { 0 }
690+
set {}
691+
}
692+
693+
func test() {}
694+
}
695+
"""
696+
)
697+
}
556698
}

0 commit comments

Comments
 (0)