Skip to content

Commit 8e9caa1

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 7784947 commit 8e9caa1

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
@@ -14,6 +14,7 @@ add_swift_host_library(_SwiftSyntaxMacros
1414
Macro.swift
1515
MacroExpansionContext.swift
1616
MacroSystem.swift
17+
MemberAttributeMacro.swift
1718
MemberDeclarationMacro.swift
1819
PeerDeclarationMacro.swift
1920
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 || macro is AccessorDeclarationMacro.Type)
112+
return !(macro is PeerDeclarationMacro.Type || macro is MemberDeclarationMacro.Type || macro is AccessorDeclarationMacro.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

@@ -392,6 +415,45 @@ extension MacroApplication {
392415
}
393416
)
394417
}
418+
419+
private func expandAttributes(
420+
for macroAttributes: [(CustomAttributeSyntax, MemberAttributeMacro.Type)],
421+
attachedTo decl: DeclSyntax,
422+
annotating member: MemberDeclListSyntax.Element
423+
) -> MemberDeclListSyntax.Element {
424+
guard let attributedDecl = member.decl.asProtocol(AttributedSyntax.self) else {
425+
return member
426+
}
427+
428+
var attributes: [CustomAttributeSyntax] = []
429+
for (attribute, attributeMacro) in macroAttributes {
430+
do {
431+
try attributes.append(
432+
contentsOf: attributeMacro.expansion(
433+
of: attribute,
434+
attachedTo: DeclSyntax(decl),
435+
annotating: member.decl,
436+
in: &context
437+
)
438+
)
439+
} catch {
440+
// Record the error
441+
context.diagnose(
442+
Diagnostic(
443+
node: Syntax(attribute),
444+
message: ThrownErrorDiagnostic(message: String(describing: error))
445+
)
446+
)
447+
}
448+
}
449+
450+
let newAttributes = attributes.reduce(attributedDecl.attributes ?? .init([])) {
451+
$0.appending(AttributeListSyntax.Element($1))
452+
}
453+
454+
let newDecl = attributedDecl.withAttributes(newAttributes).as(DeclSyntax.self)!
455+
return member.withDecl(newDecl)
456+
}
395457
}
396458

397459
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
@@ -403,6 +403,70 @@ public struct AddBackingStorage: MemberDeclarationMacro {
403403
}
404404
}
405405

406+
public struct WrapAllProperties: MemberAttributeMacro {
407+
public static func expansion(
408+
of node: CustomAttributeSyntax,
409+
attachedTo decl: DeclSyntax,
410+
annotating member: DeclSyntax,
411+
in context: inout MacroExpansionContext
412+
) throws -> [CustomAttributeSyntax] {
413+
guard member.is(VariableDeclSyntax.self) else {
414+
return []
415+
}
416+
417+
return [
418+
CustomAttributeSyntax(
419+
attributeName: SimpleTypeIdentifierSyntax(
420+
name: .identifier("Wrapper")
421+
)
422+
)
423+
.withLeadingTrivia([.newlines(1), .spaces(2)])
424+
]
425+
}
426+
}
427+
428+
public struct WrapStoredProperties: MemberAttributeMacro {
429+
public static func expansion(
430+
of node: CustomAttributeSyntax,
431+
attachedTo decl: DeclSyntax,
432+
annotating member: DeclSyntax,
433+
in context: inout MacroExpansionContext
434+
) throws -> [CustomAttributeSyntax] {
435+
guard let property = member.as(VariableDeclSyntax.self),
436+
property.bindings.count == 1
437+
else {
438+
return []
439+
}
440+
441+
let binding = property.bindings.first!
442+
switch binding.accessor {
443+
case .none:
444+
break
445+
case .accessors(let node):
446+
for accessor in node.accessors {
447+
switch accessor.accessorKind.tokenKind {
448+
case .contextualKeyword(.get), .contextualKeyword(.set):
449+
return []
450+
default:
451+
break
452+
}
453+
}
454+
break
455+
case .getter:
456+
return []
457+
}
458+
459+
return [
460+
CustomAttributeSyntax(
461+
attributeName: SimpleTypeIdentifierSyntax(
462+
name: .identifier("Wrapper")
463+
)
464+
)
465+
.withLeadingTrivia([.newlines(1), .spaces(2)])
466+
]
467+
}
468+
}
469+
406470
// MARK: Assertion helper functions
407471

408472
/// Assert that expanding the given macros in the original source produces
@@ -467,6 +531,8 @@ public let testMacros: [String: Macro.Type] = [
467531
"wrapProperty": PropertyWrapper.self,
468532
"addCompletionHandler": AddCompletionHandler.self,
469533
"addBackingStorage": AddBackingStorage.self,
534+
"wrapAllProperties": WrapAllProperties.self,
535+
"wrapStoredProperties": WrapStoredProperties.self,
470536
]
471537

472538
final class MacroSystemTests: XCTestCase {
@@ -645,4 +711,80 @@ final class MacroSystemTests: XCTestCase {
645711
"""
646712
)
647713
}
714+
715+
func testWrapAllProperties() {
716+
AssertMacroExpansion(
717+
macros: testMacros,
718+
"""
719+
@wrapAllProperties
720+
struct Point {
721+
var x: Int
722+
var y: Int
723+
var description: String { "" }
724+
var computed: Int {
725+
get { 0 }
726+
set {}
727+
}
728+
729+
func test() {}
730+
}
731+
""",
732+
"""
733+
734+
struct Point {
735+
@Wrapper
736+
var x: Int
737+
@Wrapper
738+
var y: Int
739+
@Wrapper
740+
var description: String { "" }
741+
@Wrapper
742+
var computed: Int {
743+
get { 0 }
744+
set {}
745+
}
746+
747+
func test() {}
748+
}
749+
"""
750+
)
751+
752+
AssertMacroExpansion(
753+
macros: testMacros,
754+
"""
755+
@wrapStoredProperties
756+
struct Point {
757+
var x: Int
758+
var y: Int
759+
760+
var description: String { "" }
761+
762+
var computed: Int {
763+
get { 0 }
764+
set {}
765+
}
766+
767+
func test() {}
768+
}
769+
""",
770+
"""
771+
772+
struct Point {
773+
@Wrapper
774+
var x: Int
775+
@Wrapper
776+
var y: Int
777+
778+
var description: String { "" }
779+
780+
var computed: Int {
781+
get { 0 }
782+
set {}
783+
}
784+
785+
func test() {}
786+
}
787+
"""
788+
)
789+
}
648790
}

0 commit comments

Comments
 (0)