From f96cb9d772c30a3070e03803449e8759f221648e Mon Sep 17 00:00:00 2001 From: Maxim Bazarov Date: Fri, 5 Jan 2024 17:45:07 +0100 Subject: [PATCH] Basic macro setup (#41) Introduces the first macro `@EnvironmentObservable` that removes code that is repeatable, and similar to swift `@Observable` ```swift @EnvironmentObservable final class Notes { @Persistent var name: String = "" var count: Int = 0 } ``` will expand to: Screenshot 2023-12-31 at 13 57 02 --- .github/workflows/swift-build-test.yml | 7 +++- Decide/Macros.swift | 13 ++++++ DecideMacros-Tests/Decide_Tests.swift | 12 ++++++ DecideMacros/Plugin.swift | 12 ++++++ DecideMacros/StorageMacro.swift | 57 ++++++++++++++++++++++++++ Package.swift | 28 +++++++++++-- 6 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 Decide/Macros.swift create mode 100644 DecideMacros-Tests/Decide_Tests.swift create mode 100644 DecideMacros/Plugin.swift create mode 100644 DecideMacros/StorageMacro.swift diff --git a/.github/workflows/swift-build-test.yml b/.github/workflows/swift-build-test.yml index b81914e..81015b3 100644 --- a/.github/workflows/swift-build-test.yml +++ b/.github/workflows/swift-build-test.yml @@ -16,8 +16,13 @@ jobs: build: runs-on: macos-13 steps: + - uses: swift-actions/setup-swift@v1 + with: + swift-version: "5.9" + - name: Get swift version + run: swift --version - uses: actions/checkout@v3 - name: Build run: swift build -v - name: Test - run: swift test \ No newline at end of file + run: swift test diff --git a/Decide/Macros.swift b/Decide/Macros.swift new file mode 100644 index 0000000..767af95 --- /dev/null +++ b/Decide/Macros.swift @@ -0,0 +1,13 @@ +import DecideMacros + +@attached( + member, + names: named(environment), + named(init(environment:)) +) +@attached(memberAttribute) +@attached(extension, conformances: StateRoot) +public macro EnvironmentObservable() = #externalMacro( + module: "DecideMacros", + type: "StorageMacro" +) diff --git a/DecideMacros-Tests/Decide_Tests.swift b/DecideMacros-Tests/Decide_Tests.swift new file mode 100644 index 0000000..eaef9bb --- /dev/null +++ b/DecideMacros-Tests/Decide_Tests.swift @@ -0,0 +1,12 @@ +import XCTest +import Decide + +final class Decide_Tests: XCTestCase { + @EnvironmentObservable + final class Notes { + @Persistent + var name: String = "" + + var count: Int = 0 + } +} diff --git a/DecideMacros/Plugin.swift b/DecideMacros/Plugin.swift new file mode 100644 index 0000000..09cc03b --- /dev/null +++ b/DecideMacros/Plugin.swift @@ -0,0 +1,12 @@ +#if canImport(SwiftCompilerPlugin) +import SwiftCompilerPlugin +import SwiftSyntaxMacros + +@main +struct DecideCompilerPlugin: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + StorageMacro.self, +// AtomicPropertyMacro.self, + ] +} +#endif diff --git a/DecideMacros/StorageMacro.swift b/DecideMacros/StorageMacro.swift new file mode 100644 index 0000000..00f94de --- /dev/null +++ b/DecideMacros/StorageMacro.swift @@ -0,0 +1,57 @@ +import SwiftSyntax +import SwiftSyntaxMacros + +public protocol ObservableState { + init() +} + +public struct StorageMacro: MemberMacro, MemberAttributeMacro { + public static func expansion( + of node: SwiftSyntax.AttributeSyntax, + attachedTo declaration: some SwiftSyntax.DeclGroupSyntax, + providingAttributesFor member: some SwiftSyntax.DeclSyntaxProtocol, + in context: some SwiftSyntaxMacros.MacroExpansionContext + ) throws -> [SwiftSyntax.AttributeSyntax] { + return [ + AttributeSyntax( + attributeName: IdentifierTypeSyntax( + name: .identifier("ObservableValue") + ) + ) + ] + } + + // MARK: - MemberMacro + public static func expansion( + of node: AttributeSyntax, + providingMembersOf declaration: some DeclGroupSyntax, + in context: some MacroExpansionContext + ) throws -> [DeclSyntax] { + guard let _ = declaration.asProtocol(NamedDeclSyntax.self) else { + return [] + } + + let environment: DeclSyntax = + """ + public unowned let environment: Decide.SharedEnvironment + public init(environment: Decide.SharedEnvironment) { + self.environment = environment + } + """ + + return [environment] + } +} + + +extension StorageMacro: ExtensionMacro { + public static func expansion( + of node: AttributeSyntax, + attachedTo declaration: some DeclGroupSyntax, + providingExtensionsOf type: some TypeSyntaxProtocol, + conformingTo protocols: [TypeSyntax], + in context: some MacroExpansionContext + ) throws -> [ExtensionDeclSyntax] { + [try ExtensionDeclSyntax("extension \(type): Decide.StateRoot {}")] + } +} diff --git a/Package.swift b/Package.swift index f41b63e..85af361 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.7 +// swift-tools-version:5.9 //===----------------------------------------------------------------------===// // // This source file is part of the Decide package open source project @@ -14,6 +14,7 @@ //===----------------------------------------------------------------------===// import PackageDescription +import CompilerPluginSupport let package = Package( name: "Decide", @@ -28,14 +29,25 @@ let package = Package( .library(name: "DecideTesting", targets: ["DecideTesting"]), ], dependencies: [ + // Depend on the Swift 5.9 release of SwiftSyntax + .package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"), ], targets: [ - // - Decide - .target( name: "Decide", - dependencies: [], + dependencies: [ + "DecideMacros" + ], path: "Decide" ), + .macro( + name: "DecideMacros", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax") + ], + path: "DecideMacros" + ), .testTarget( name: "Decide-Tests", dependencies: [ @@ -50,5 +62,15 @@ let package = Package( dependencies: ["Decide"], path: "DecideTesting" ), + // Macros Tests + .testTarget( + name: "DecideMacros-Tests", + dependencies: [ + "Decide", + "DecideMacros", + .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), + ], + path: "DecideMacros-Tests" + ), ] )