Skip to content

[Macros] Add accessor declaration macros, to add accessors to a stored property #1222

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Sources/_SwiftSyntaxMacros/AccessorDeclarationMacro.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftSyntax

/// Describes a macro that adds accessors to a given declaration.
public protocol AccessorDeclarationMacro: DeclarationMacro {
/// Expand a macro that's expressed as a custom attribute attached to
/// the given declaration. The result is a set of accessors for the
/// declaration.
static func expansion(
of node: CustomAttributeSyntax,
attachedTo declaration: DeclSyntax,
in context: inout MacroExpansionContext
) throws -> [AccessorDeclSyntax]
}
1 change: 1 addition & 0 deletions Sources/_SwiftSyntaxMacros/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# See http://swift.org/CONTRIBUTORS.txt for Swift project authors

add_swift_host_library(_SwiftSyntaxMacros
AccessorDeclarationMacro.swift
DeclarationMacro.swift
ExpressionMacro.swift
FreestandingDeclarationMacro.swift
Expand Down
56 changes: 55 additions & 1 deletion Sources/_SwiftSyntaxMacros/MacroSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class MacroApplication: SyntaxRewriter {
return true
}

return !(macro is PeerDeclarationMacro.Type || macro is MemberDeclarationMacro.Type)
return !(macro is PeerDeclarationMacro.Type || macro is MemberDeclarationMacro.Type || macro is AccessorDeclarationMacro.Type)
}

if newAttributes.isEmpty {
Expand Down Expand Up @@ -254,6 +254,60 @@ class MacroApplication: SyntaxRewriter {
override func visit(_ node: ExtensionDeclSyntax) -> DeclSyntax {
return visit(declGroup: node)
}

// Properties
override func visit(_ node: VariableDeclSyntax) -> DeclSyntax {
let visitedNode = super.visit(node)
guard let visitedVarDecl = visitedNode.as(VariableDeclSyntax.self) else {
return visitedNode
}

guard let binding = visitedVarDecl.bindings.first,
visitedVarDecl.bindings.count == 1
else {
return DeclSyntax(node)
}

var accessors: [AccessorDeclSyntax] = []

let accessorMacroAttributes = getMacroAttributes(attachedTo: DeclSyntax(node), ofType: AccessorDeclarationMacro.Type.self)
for (accessorAttr, accessorMacro) in accessorMacroAttributes {
do {
let newAccessors = try accessorMacro.expansion(
of: accessorAttr,
attachedTo: DeclSyntax(visitedNode),
in: &context
)

accessors.append(contentsOf: newAccessors)
} catch {
// FIXME: record the error
}
}

if accessors.isEmpty {
return visitedNode
}

return DeclSyntax(
visitedVarDecl.withBindings(
visitedVarDecl.bindings.replacing(
childAt: 0,
with: binding.withAccessor(
.accessors(
.init(
leftBrace: .leftBraceToken(leadingTrivia: .space),
accessors: .init(accessors),
rightBrace: .rightBraceToken(leadingTrivia: .newline)
)
)
)
)
)
)
}

// Subscripts
}

extension MacroApplication {
Expand Down
92 changes: 92 additions & 0 deletions Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,75 @@ struct DefineBitwidthNumberedStructsMacro: FreestandingDeclarationMacro {
}
}

public struct PropertyWrapper {}

extension PropertyWrapper: AccessorDeclarationMacro {
public static func expansion(
of node: CustomAttributeSyntax,
attachedTo declaration: DeclSyntax,
in context: inout MacroExpansionContext
) throws -> [AccessorDeclSyntax] {
guard let varDecl = declaration.as(VariableDeclSyntax.self),
let binding = varDecl.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
binding.accessor == nil
else {
return []
}

return [
"""

get {
_\(identifier).wrappedValue
}
""",
"""

set {
_\(identifier).wrappedValue = newValue
}
""",
]
}
}

extension PropertyWrapper: PeerDeclarationMacro {
public static func expansion(
of node: CustomAttributeSyntax,
attachedTo declaration: DeclSyntax,
in context: inout MacroExpansionContext
) throws -> [SwiftSyntax.DeclSyntax] {
guard let varDecl = declaration.as(VariableDeclSyntax.self),
let binding = varDecl.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
let type = binding.typeAnnotation?.type,
binding.accessor == nil
else {
return []
}

guard let wrapperTypeNameExpr = node.argumentList?.first?.expression,
let stringLiteral = wrapperTypeNameExpr.as(StringLiteralExprSyntax.self),
stringLiteral.segments.count == 1,
case let .stringSegment(wrapperTypeNameSegment)? = stringLiteral.segments.first
else {
return []
}

let storageType: TypeSyntax = "\(wrapperTypeNameSegment.content)<\(type)>"
let storageName = "_\(identifier)"

return [
"""

private var \(raw: storageName): \(storageType)

"""
]
}
}

public struct AddCompletionHandler: PeerDeclarationMacro {
public static func expansion(
of node: CustomAttributeSyntax,
Expand Down Expand Up @@ -395,6 +464,7 @@ public let testMacros: [String: Macro.Type] = [
"stringify": StringifyMacro.self,
"myError": ErrorMacro.self,
"bitwidthNumberedStructs": DefineBitwidthNumberedStructsMacro.self,
"wrapProperty": PropertyWrapper.self,
"addCompletionHandler": AddCompletionHandler.self,
"addBackingStorage": AddBackingStorage.self,
]
Expand Down Expand Up @@ -515,6 +585,28 @@ final class MacroSystemTests: XCTestCase {
)
}

func testPropertyWrapper() {
AssertMacroExpansion(
macros: testMacros,
"""
@wrapProperty("MyWrapperType")
var x: Int
""",
"""

var x: Int {
get {
_x.wrappedValue
}
set {
_x.wrappedValue = newValue
}
}
private var _x: MyWrapperType<Int>
"""
)
}

func testAddCompletionHandler() {
AssertMacroExpansion(
macros: testMacros,
Expand Down