Skip to content

Commit 7784947

Browse files
authored
Merge pull request #1222 from DougGregor/accessor-declaration-macros
2 parents 251492d + afce931 commit 7784947

File tree

4 files changed

+170
-1
lines changed

4 files changed

+170
-1
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
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+
12+
/// Describes a macro that adds accessors to a given declaration.
13+
public protocol AccessorDeclarationMacro: DeclarationMacro {
14+
/// Expand a macro that's expressed as a custom attribute attached to
15+
/// the given declaration. The result is a set of accessors for the
16+
/// declaration.
17+
static func expansion(
18+
of node: CustomAttributeSyntax,
19+
attachedTo declaration: DeclSyntax,
20+
in context: inout MacroExpansionContext
21+
) throws -> [AccessorDeclSyntax]
22+
}

Sources/_SwiftSyntaxMacros/CMakeLists.txt

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

99
add_swift_host_library(_SwiftSyntaxMacros
10+
AccessorDeclarationMacro.swift
1011
DeclarationMacro.swift
1112
ExpressionMacro.swift
1213
FreestandingDeclarationMacro.swift

Sources/_SwiftSyntaxMacros/MacroSystem.swift

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class MacroApplication: SyntaxRewriter {
106106
return true
107107
}
108108

109-
return !(macro is PeerDeclarationMacro.Type || macro is MemberDeclarationMacro.Type)
109+
return !(macro is PeerDeclarationMacro.Type || macro is MemberDeclarationMacro.Type || macro is AccessorDeclarationMacro.Type)
110110
}
111111

112112
if newAttributes.isEmpty {
@@ -254,6 +254,60 @@ class MacroApplication: SyntaxRewriter {
254254
override func visit(_ node: ExtensionDeclSyntax) -> DeclSyntax {
255255
return visit(declGroup: node)
256256
}
257+
258+
// Properties
259+
override func visit(_ node: VariableDeclSyntax) -> DeclSyntax {
260+
let visitedNode = super.visit(node)
261+
guard let visitedVarDecl = visitedNode.as(VariableDeclSyntax.self) else {
262+
return visitedNode
263+
}
264+
265+
guard let binding = visitedVarDecl.bindings.first,
266+
visitedVarDecl.bindings.count == 1
267+
else {
268+
return DeclSyntax(node)
269+
}
270+
271+
var accessors: [AccessorDeclSyntax] = []
272+
273+
let accessorMacroAttributes = getMacroAttributes(attachedTo: DeclSyntax(node), ofType: AccessorDeclarationMacro.Type.self)
274+
for (accessorAttr, accessorMacro) in accessorMacroAttributes {
275+
do {
276+
let newAccessors = try accessorMacro.expansion(
277+
of: accessorAttr,
278+
attachedTo: DeclSyntax(visitedNode),
279+
in: &context
280+
)
281+
282+
accessors.append(contentsOf: newAccessors)
283+
} catch {
284+
// FIXME: record the error
285+
}
286+
}
287+
288+
if accessors.isEmpty {
289+
return visitedNode
290+
}
291+
292+
return DeclSyntax(
293+
visitedVarDecl.withBindings(
294+
visitedVarDecl.bindings.replacing(
295+
childAt: 0,
296+
with: binding.withAccessor(
297+
.accessors(
298+
.init(
299+
leftBrace: .leftBraceToken(leadingTrivia: .space),
300+
accessors: .init(accessors),
301+
rightBrace: .rightBraceToken(leadingTrivia: .newline)
302+
)
303+
)
304+
)
305+
)
306+
)
307+
)
308+
}
309+
310+
// Subscripts
257311
}
258312

259313
extension MacroApplication {

Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,75 @@ struct DefineBitwidthNumberedStructsMacro: FreestandingDeclarationMacro {
208208
}
209209
}
210210

211+
public struct PropertyWrapper {}
212+
213+
extension PropertyWrapper: AccessorDeclarationMacro {
214+
public static func expansion(
215+
of node: CustomAttributeSyntax,
216+
attachedTo declaration: DeclSyntax,
217+
in context: inout MacroExpansionContext
218+
) throws -> [AccessorDeclSyntax] {
219+
guard let varDecl = declaration.as(VariableDeclSyntax.self),
220+
let binding = varDecl.bindings.first,
221+
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
222+
binding.accessor == nil
223+
else {
224+
return []
225+
}
226+
227+
return [
228+
"""
229+
230+
get {
231+
_\(identifier).wrappedValue
232+
}
233+
""",
234+
"""
235+
236+
set {
237+
_\(identifier).wrappedValue = newValue
238+
}
239+
""",
240+
]
241+
}
242+
}
243+
244+
extension PropertyWrapper: PeerDeclarationMacro {
245+
public static func expansion(
246+
of node: CustomAttributeSyntax,
247+
attachedTo declaration: DeclSyntax,
248+
in context: inout MacroExpansionContext
249+
) throws -> [SwiftSyntax.DeclSyntax] {
250+
guard let varDecl = declaration.as(VariableDeclSyntax.self),
251+
let binding = varDecl.bindings.first,
252+
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
253+
let type = binding.typeAnnotation?.type,
254+
binding.accessor == nil
255+
else {
256+
return []
257+
}
258+
259+
guard let wrapperTypeNameExpr = node.argumentList?.first?.expression,
260+
let stringLiteral = wrapperTypeNameExpr.as(StringLiteralExprSyntax.self),
261+
stringLiteral.segments.count == 1,
262+
case let .stringSegment(wrapperTypeNameSegment)? = stringLiteral.segments.first
263+
else {
264+
return []
265+
}
266+
267+
let storageType: TypeSyntax = "\(wrapperTypeNameSegment.content)<\(type)>"
268+
let storageName = "_\(identifier)"
269+
270+
return [
271+
"""
272+
273+
private var \(raw: storageName): \(storageType)
274+
275+
"""
276+
]
277+
}
278+
}
279+
211280
public struct AddCompletionHandler: PeerDeclarationMacro {
212281
public static func expansion(
213282
of node: CustomAttributeSyntax,
@@ -395,6 +464,7 @@ public let testMacros: [String: Macro.Type] = [
395464
"stringify": StringifyMacro.self,
396465
"myError": ErrorMacro.self,
397466
"bitwidthNumberedStructs": DefineBitwidthNumberedStructsMacro.self,
467+
"wrapProperty": PropertyWrapper.self,
398468
"addCompletionHandler": AddCompletionHandler.self,
399469
"addBackingStorage": AddBackingStorage.self,
400470
]
@@ -515,6 +585,28 @@ final class MacroSystemTests: XCTestCase {
515585
)
516586
}
517587

588+
func testPropertyWrapper() {
589+
AssertMacroExpansion(
590+
macros: testMacros,
591+
"""
592+
@wrapProperty("MyWrapperType")
593+
var x: Int
594+
""",
595+
"""
596+
597+
var x: Int {
598+
get {
599+
_x.wrappedValue
600+
}
601+
set {
602+
_x.wrappedValue = newValue
603+
}
604+
}
605+
private var _x: MyWrapperType<Int>
606+
"""
607+
)
608+
}
609+
518610
func testAddCompletionHandler() {
519611
AssertMacroExpansion(
520612
macros: testMacros,

0 commit comments

Comments
 (0)