Skip to content

Commit 07dc1eb

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 0d2a3fd commit 07dc1eb

File tree

4 files changed

+239
-3
lines changed

4 files changed

+239
-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: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ 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: [
70+
([(CustomAttributeSyntax, MemberAttributeMacro.Type)], DeclSyntax)
71+
] = []
72+
6873
init(
6974
macroSystem: MacroSystem,
7075
context: MacroExpansionContext
@@ -106,7 +111,7 @@ class MacroApplication: SyntaxRewriter {
106111
return true
107112
}
108113

109-
return !(macro is PeerDeclarationMacro.Type || macro is MemberDeclarationMacro.Type)
114+
return !(macro is PeerDeclarationMacro.Type || macro is MemberDeclarationMacro.Type || macro is MemberAttributeMacro.Type)
110115
}
111116

112117
if newAttributes.isEmpty {
@@ -203,9 +208,21 @@ class MacroApplication: SyntaxRewriter {
203208
continue
204209
}
205210

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

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

@@ -338,6 +361,43 @@ 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(contentsOf: attributeMacro.expansion(
378+
of: attribute,
379+
attachedTo: DeclSyntax(decl),
380+
annotating: member.decl,
381+
in: &context
382+
))
383+
} catch {
384+
// Record the error
385+
context.diagnose(
386+
Diagnostic(
387+
node: Syntax(attribute),
388+
message: ThrownErrorDiagnostic(message: String(describing: error))
389+
)
390+
)
391+
}
392+
}
393+
394+
let newAttributes = attributes.reduce(attributedDecl.attributes ?? .init([])) {
395+
$0.appending(AttributeListSyntax.Element($1))
396+
}
397+
398+
let newDecl = attributedDecl.withAttributes(newAttributes).as(DeclSyntax.self)!
399+
return member.withDecl(newDecl)
400+
}
341401
}
342402

343403
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: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,69 @@ 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 else {
368+
return []
369+
}
370+
371+
let binding = property.bindings.first!
372+
switch binding.accessor {
373+
case .none:
374+
break
375+
case .accessors(let node):
376+
for accessor in node.accessors {
377+
switch accessor.accessorKind.tokenKind {
378+
case .contextualKeyword("get"), .contextualKeyword("set"):
379+
return []
380+
default:
381+
break
382+
}
383+
}
384+
break
385+
case .getter:
386+
return []
387+
}
388+
389+
return [
390+
CustomAttributeSyntax(
391+
attributeName: SimpleTypeIdentifierSyntax(
392+
name: .identifier("Wrapper")
393+
)
394+
)
395+
.withLeadingTrivia([.newlines(1), .spaces(2)])
396+
]
397+
}
398+
}
399+
337400
// MARK: Assertion helper functions
338401

339402
/// Assert that expanding the given macros in the original source produces
@@ -397,6 +460,8 @@ public let testMacros: [String: Macro.Type] = [
397460
"bitwidthNumberedStructs": DefineBitwidthNumberedStructsMacro.self,
398461
"addCompletionHandler": AddCompletionHandler.self,
399462
"addBackingStorage": AddBackingStorage.self,
463+
"wrapAllProperties": WrapAllProperties.self,
464+
"wrapStoredProperties": WrapStoredProperties.self,
400465
]
401466

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

0 commit comments

Comments
 (0)