From 0d1e0a2df3e7ee1f18f96bb45c6761a0ab008916 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 11 Jan 2023 10:59:00 -0800 Subject: [PATCH 1/3] [Macros] Add accessor declaration macros, to add accessors to a stored property --- .../AccessorDeclarationMacro.swift | 22 +++++++ Sources/_SwiftSyntaxMacros/CMakeLists.txt | 1 + Sources/_SwiftSyntaxMacros/MacroSystem.swift | 50 +++++++++++++++- .../MacroSystemTests.swift | 59 +++++++++++++++++++ 4 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 Sources/_SwiftSyntaxMacros/AccessorDeclarationMacro.swift diff --git a/Sources/_SwiftSyntaxMacros/AccessorDeclarationMacro.swift b/Sources/_SwiftSyntaxMacros/AccessorDeclarationMacro.swift new file mode 100644 index 00000000000..1daff746879 --- /dev/null +++ b/Sources/_SwiftSyntaxMacros/AccessorDeclarationMacro.swift @@ -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] +} diff --git a/Sources/_SwiftSyntaxMacros/CMakeLists.txt b/Sources/_SwiftSyntaxMacros/CMakeLists.txt index 4b2d8e48fa2..6aacc9e2c19 100644 --- a/Sources/_SwiftSyntaxMacros/CMakeLists.txt +++ b/Sources/_SwiftSyntaxMacros/CMakeLists.txt @@ -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 diff --git a/Sources/_SwiftSyntaxMacros/MacroSystem.swift b/Sources/_SwiftSyntaxMacros/MacroSystem.swift index 4835f43faaa..51541236624 100644 --- a/Sources/_SwiftSyntaxMacros/MacroSystem.swift +++ b/Sources/_SwiftSyntaxMacros/MacroSystem.swift @@ -106,7 +106,9 @@ 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 { @@ -254,6 +256,52 @@ 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 { diff --git a/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift b/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift index f34383471f6..d4be09d66a5 100644 --- a/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift +++ b/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift @@ -208,6 +208,44 @@ struct DefineBitwidthNumberedStructsMacro: FreestandingDeclarationMacro { } } +public struct 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, + 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 [] + } + + return [ + """ + + get { + _\(identifier).wrappedValue + } + """, + """ + + set { + _\(identifier).wrappedValue = newValue + } + """ + ] + } +} + public struct AddCompletionHandler: PeerDeclarationMacro { public static func expansion( of node: CustomAttributeSyntax, @@ -395,6 +433,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, ] @@ -515,6 +554,26 @@ final class MacroSystemTests: XCTestCase { ) } + func testPropertyWrapper() { + AssertMacroExpansion( + macros: testMacros, + """ + @wrapProperty("MyWrapperType") + var x: Int + """, + """ + + var x: Int { + get { + _x.wrappedValue + } + set { + _x.wrappedValue = newValue + } + } + """) + } + func testAddCompletionHandler() { AssertMacroExpansion( macros: testMacros, From 8e7c7af64493ddd25428d84754df62b5dd48de3a Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 11 Jan 2023 11:11:08 -0800 Subject: [PATCH 2/3] [Macros] Extend the property-wrapper example to also generate backing storage The same use of the macro `@wrapProperty` expands in two ways, adding accessors and the storage declaration. --- .../MacroSystemTests.swift | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift b/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift index d4be09d66a5..9a5264bf47a 100644 --- a/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift +++ b/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift @@ -208,7 +208,9 @@ struct DefineBitwidthNumberedStructsMacro: FreestandingDeclarationMacro { } } -public struct PropertyWrapper: AccessorDeclarationMacro { +public struct PropertyWrapper { } + +extension PropertyWrapper: AccessorDeclarationMacro { public static func expansion( of node: CustomAttributeSyntax, attachedTo declaration: DeclSyntax, @@ -217,18 +219,10 @@ public struct PropertyWrapper: AccessorDeclarationMacro { 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 [] - } - return [ """ @@ -246,6 +240,40 @@ public struct PropertyWrapper: AccessorDeclarationMacro { } } +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, @@ -571,6 +599,7 @@ final class MacroSystemTests: XCTestCase { _x.wrappedValue = newValue } } + private var _x: MyWrapperType """) } From afce931f9b9f47309a45bc9cca07e774d68df864 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Wed, 11 Jan 2023 11:16:40 -0800 Subject: [PATCH 3/3] Reformat --- Sources/_SwiftSyntaxMacros/MacroSystem.swift | 26 +++++++++------ .../MacroSystemTests.swift | 32 +++++++++++-------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/Sources/_SwiftSyntaxMacros/MacroSystem.swift b/Sources/_SwiftSyntaxMacros/MacroSystem.swift index 51541236624..45406581d6f 100644 --- a/Sources/_SwiftSyntaxMacros/MacroSystem.swift +++ b/Sources/_SwiftSyntaxMacros/MacroSystem.swift @@ -106,9 +106,7 @@ class MacroApplication: SyntaxRewriter { return true } - return !(macro is PeerDeclarationMacro.Type || - macro is MemberDeclarationMacro.Type || - macro is AccessorDeclarationMacro.Type) + return !(macro is PeerDeclarationMacro.Type || macro is MemberDeclarationMacro.Type || macro is AccessorDeclarationMacro.Type) } if newAttributes.isEmpty { @@ -265,7 +263,8 @@ class MacroApplication: SyntaxRewriter { } guard let binding = visitedVarDecl.bindings.first, - visitedVarDecl.bindings.count == 1 else { + visitedVarDecl.bindings.count == 1 + else { return DeclSyntax(node) } @@ -292,12 +291,19 @@ class MacroApplication: SyntaxRewriter { 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)) - )))) + visitedVarDecl.bindings.replacing( + childAt: 0, + with: binding.withAccessor( + .accessors( + .init( + leftBrace: .leftBraceToken(leadingTrivia: .space), + accessors: .init(accessors), + rightBrace: .rightBraceToken(leadingTrivia: .newline) + ) + ) + ) + ) + ) ) } diff --git a/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift b/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift index 9a5264bf47a..c2642cfa04d 100644 --- a/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift +++ b/Tests/SwiftSyntaxMacrosTest/MacroSystemTests.swift @@ -208,7 +208,7 @@ struct DefineBitwidthNumberedStructsMacro: FreestandingDeclarationMacro { } } -public struct PropertyWrapper { } +public struct PropertyWrapper {} extension PropertyWrapper: AccessorDeclarationMacro { public static func expansion( @@ -217,9 +217,10 @@ extension PropertyWrapper: AccessorDeclarationMacro { 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 { + let binding = varDecl.bindings.first, + let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier, + binding.accessor == nil + else { return [] } @@ -235,7 +236,7 @@ extension PropertyWrapper: AccessorDeclarationMacro { set { _\(identifier).wrappedValue = newValue } - """ + """, ] } } @@ -247,17 +248,19 @@ extension PropertyWrapper: PeerDeclarationMacro { 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 { + 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 { + let stringLiteral = wrapperTypeNameExpr.as(StringLiteralExprSyntax.self), + stringLiteral.segments.count == 1, + case let .stringSegment(wrapperTypeNameSegment)? = stringLiteral.segments.first + else { return [] } @@ -461,7 +464,7 @@ public let testMacros: [String: Macro.Type] = [ "stringify": StringifyMacro.self, "myError": ErrorMacro.self, "bitwidthNumberedStructs": DefineBitwidthNumberedStructsMacro.self, - "wrapProperty" : PropertyWrapper.self, + "wrapProperty": PropertyWrapper.self, "addCompletionHandler": AddCompletionHandler.self, "addBackingStorage": AddBackingStorage.self, ] @@ -600,7 +603,8 @@ final class MacroSystemTests: XCTestCase { } } private var _x: MyWrapperType - """) + """ + ) } func testAddCompletionHandler() {