Skip to content

Commit

Permalink
Incorporate ObservableMacro
Browse files Browse the repository at this point in the history
  • Loading branch information
Matejkob committed Oct 22, 2023
1 parent 0ed3f03 commit db03afc
Show file tree
Hide file tree
Showing 2 changed files with 305 additions and 0 deletions.
173 changes: 173 additions & 0 deletions Tests/MacroTestingTests/MacroExamples/ObservableMacro.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// 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
import SwiftSyntaxMacros

private extension DeclSyntaxProtocol {
var isObservableStoredProperty: Bool {
if let property = self.as(VariableDeclSyntax.self),
let binding = property.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
identifier.text != "_registrar", identifier.text != "_storage",
binding.accessorBlock == nil
{
return true
}

return false
}
}

public struct ObservableMacro: MemberMacro, MemberAttributeMacro {

// MARK: - MemberMacro

public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard let identified = declaration.asProtocol(NamedDeclSyntax.self) else {
return []
}

let parentName = identified.name

let registrar: DeclSyntax =
"""
let _registrar = ObservationRegistrar<\(parentName)>()
"""

let addObserver: DeclSyntax =
"""
public nonisolated func addObserver(_ observer: some Observer<\(parentName)>) {
_registrar.addObserver(observer)
}
"""

let removeObserver: DeclSyntax =
"""
public nonisolated func removeObserver(_ observer: some Observer<\(parentName)>) {
_registrar.removeObserver(observer)
}
"""

let withTransaction: DeclSyntax =
"""
private func withTransaction<T>(_ apply: () throws -> T) rethrows -> T {
_registrar.beginAccess()
defer { _registrar.endAccess() }
return try apply()
}
"""

let memberList = declaration.memberBlock.members.filter {
$0.decl.isObservableStoredProperty
}

let storageStruct: DeclSyntax =
"""
private struct Storage {
\(memberList)
}
"""

let storage: DeclSyntax =
"""
private var _storage = Storage()
"""

return [
registrar,
addObserver,
removeObserver,
withTransaction,
storageStruct,
storage,
]
}

// MARK: - MemberAttributeMacro

public static func expansion(
of node: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
providingAttributesFor member: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [SwiftSyntax.AttributeSyntax] {
guard member.isObservableStoredProperty else {
return []
}

return [
AttributeSyntax(
attributeName: IdentifierTypeSyntax(
name: .identifier("ObservableProperty")
)
)
]
}

}

extension ObservableMacro: 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): Observable {}")]
}
}

public struct ObservablePropertyMacro: AccessorMacro {
public static func expansion(
of node: AttributeSyntax,
providingAccessorsOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [AccessorDeclSyntax] {
guard let property = declaration.as(VariableDeclSyntax.self),
let binding = property.bindings.first,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier,
binding.accessorBlock == nil
else {
return []
}

let getAccessor: AccessorDeclSyntax =
"""
get {
_registrar.beginAccess(\\.\(identifier))
defer { _registrar.endAccess() }
return _storage.\(identifier)
}
"""

let setAccessor: AccessorDeclSyntax =
"""
set {
_registrar.beginAccess(\\.\(identifier))
_registrar.register(observable: self, willSet: \\.\(identifier), to: newValue)
defer {
_registrar.register(observable: self, didSet: \\.\(identifier))
_registrar.endAccess()
}
_storage.\(identifier) = newValue
}
"""

return [getAccessor, setAccessor]
}
}
132 changes: 132 additions & 0 deletions Tests/MacroTestingTests/ObservableMacroTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import MacroTesting
import XCTest

final class ObservableMacroTests: XCTestCase {
override func invokeTest() {
withMacroTesting(
macros: [
"Observable": ObservableMacro.self,
"ObservableProperty": ObservablePropertyMacro.self
]
) {
super.invokeTest()
}
}

func testExpansion() {
assertMacro {
"""
@Observable
final class Dog {
var name: String?
var treat: Treat?
var isHappy: Bool = true
init() {}
func bark() {
print("bork bork")
}
}
"""
} expansion: {
#"""
final class Dog {
var name: String? {
get {
_registrar.beginAccess(\.name)
defer {
_registrar.endAccess()
}
return _storage.name
}
set {
_registrar.beginAccess(\.name)
_registrar.register(observable: self, willSet: \.name, to: newValue)
defer {
_registrar.register(observable: self, didSet: \.name)
_registrar.endAccess()
}
_storage.name = newValue
}
}
var treat: Treat? {
get {
_registrar.beginAccess(\.treat)
defer {
_registrar.endAccess()
}
return _storage.treat
}
set {
_registrar.beginAccess(\.treat)
_registrar.register(observable: self, willSet: \.treat, to: newValue)
defer {
_registrar.register(observable: self, didSet: \.treat)
_registrar.endAccess()
}
_storage.treat = newValue
}
}
var isHappy: Bool = true {
get {
_registrar.beginAccess(\.isHappy)
defer {
_registrar.endAccess()
}
return _storage.isHappy
}
set {
_registrar.beginAccess(\.isHappy)
_registrar.register(observable: self, willSet: \.isHappy, to: newValue)
defer {
_registrar.register(observable: self, didSet: \.isHappy)
_registrar.endAccess()
}
_storage.isHappy = newValue
}
}
init() {}
func bark() {
print("bork bork")
}
let _registrar = ObservationRegistrar<Dog >()
public nonisolated func addObserver(_ observer: some Observer<Dog >) {
_registrar.addObserver(observer)
}
public nonisolated func removeObserver(_ observer: some Observer<Dog >) {
_registrar.removeObserver(observer)
}
private func withTransaction<T>(_ apply: () throws -> T) rethrows -> T {
_registrar.beginAccess()
defer {
_registrar.endAccess()
}
return try apply()
}
private struct Storage {
var name: String?
var treat: Treat?
var isHappy: Bool = true
}
private var _storage = Storage()
}
extension Dog: Observable {
}
"""#
}
}
}

0 comments on commit db03afc

Please sign in to comment.