From 74a003f8ac9f0f6d9907ddb973e9218453348bcc Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Wed, 28 Feb 2024 06:41:50 -0800 Subject: [PATCH 01/12] Add Swift macro (requires Swift v5.9) --- Package.resolved | 69 +++++++------ Package@swift-5.9.swift | 50 ++++++++++ README.md | 37 ++++--- Swift/Sources/StateMachine/Macros.swift | 10 ++ .../Macros/StateMachineHashableMacro.swift | 93 ++++++++++++++++++ .../StateMachineHashableMacroError.swift | 19 ++++ .../StateMachineMacros.swift | 17 ++++ .../StateMachineMacrosTests.swift | 97 +++++++++++++++++++ .../StateMachineTests/StateMachineTests.swift | 6 +- .../StateMachine_Matter_Tests.swift | 6 +- .../StateMachine_Turnstile_Tests.swift | 66 +------------ 11 files changed, 356 insertions(+), 114 deletions(-) create mode 100644 Package@swift-5.9.swift create mode 100644 Swift/Sources/StateMachine/Macros.swift create mode 100644 Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift create mode 100644 Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift create mode 100644 Swift/Sources/StateMachineMacros/StateMachineMacros.swift create mode 100644 Swift/Tests/StateMachineTests/StateMachineMacrosTests.swift diff --git a/Package.resolved b/Package.resolved index 917f026..aec8385 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,34 +1,41 @@ { - "object": { - "pins": [ - { - "package": "CwlCatchException", - "repositoryURL": "https://github.com/mattgallagher/CwlCatchException.git", - "state": { - "branch": null, - "revision": "682841464136f8c66e04afe5dbd01ab51a3a56f2", - "version": "2.1.0" - } - }, - { - "package": "CwlPreconditionTesting", - "repositoryURL": "https://github.com/mattgallagher/CwlPreconditionTesting.git", - "state": { - "branch": null, - "revision": "02b7a39a99c4da27abe03cab2053a9034379639f", - "version": "2.0.0" - } - }, - { - "package": "Nimble", - "repositoryURL": "https://github.com/Quick/Nimble.git", - "state": { - "branch": null, - "revision": "af1730dde4e6c0d45bf01b99f8a41713ce536790", - "version": "9.2.0" - } + "pins" : [ + { + "identity" : "cwlcatchexception", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattgallagher/CwlCatchException.git", + "state" : { + "revision" : "3b123999de19bf04905bc1dfdb76f817b0f2cc00", + "version" : "2.1.2" } - ] - }, - "version": 1 + }, + { + "identity" : "cwlpreconditiontesting", + "kind" : "remoteSourceControl", + "location" : "https://github.com/mattgallagher/CwlPreconditionTesting.git", + "state" : { + "revision" : "dc9af4781f2afdd1e68e90f80b8603be73ea7abc", + "version" : "2.2.0" + } + }, + { + "identity" : "nimble", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Quick/Nimble.git", + "state" : { + "revision" : "efe11bbca024b57115260709b5c05e01131470d0", + "version" : "13.2.1" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", + "version" : "509.1.1" + } + } + ], + "version" : 2 } diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift new file mode 100644 index 0000000..8dd7734 --- /dev/null +++ b/Package@swift-5.9.swift @@ -0,0 +1,50 @@ +// swift-tools-version:5.9 + +import PackageDescription +import CompilerPluginSupport + +let package = Package( + name: "StateMachine", + platforms: [ + .macOS(.v10_15), + .iOS(.v13), + .tvOS(.v13), + .watchOS(.v5), + ], + products: [ + .library( + name: "StateMachine", + targets: ["StateMachine"]), + ], + dependencies: [ + .package( + url: "https://github.com/apple/swift-syntax.git", + from: "509.1.0"), + .package( + url: "https://github.com/Quick/Nimble.git", + from: "13.2.0"), + ], + targets: [ + .target( + name: "StateMachine", + dependencies: ["StateMachineMacros"], + path: "Swift/Sources/StateMachine"), + .macro( + name: "StateMachineMacros", + dependencies: [ + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftCompilerPlugin", package: "swift-syntax"), + ], + path: "Swift/Sources/StateMachineMacros"), + .testTarget( + name: "StateMachineTests", + dependencies: [ + "StateMachine", + "StateMachineMacros", + .product(name: "SwiftSyntaxMacros", package: "swift-syntax"), + .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), + "Nimble", + ], + path: "Swift/Tests/StateMachineTests"), + ] +) diff --git a/README.md b/README.md index 4cd1e87..709f072 100755 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ The examples below create a `StateMachine` from the following state diagram for Define states, events and side effects: -~~~kotlin +```kotlin sealed class State { object Solid : State() object Liquid : State() @@ -36,11 +36,11 @@ sealed class SideEffect { object LogVaporized : SideEffect() object LogCondensed : SideEffect() } -~~~ +``` Initialize state machine and declare state transitions: -~~~kotlin +```kotlin val stateMachine = StateMachine.create { initialState(State.Solid) state { @@ -71,11 +71,11 @@ val stateMachine = StateMachine.create { } } } -~~~ +``` Perform state transitions: -~~~kotlin +```kotlin assertThat(stateMachine.state).isEqualTo(Solid) // When @@ -87,7 +87,7 @@ assertThat(transition).isEqualTo( StateMachine.Transition.Valid(Solid, OnMelted, Liquid, LogMelted) ) then(logger).should().log(ON_MELTED_MESSAGE) -~~~ +``` ## Swift Usage @@ -103,11 +103,13 @@ class MyExample: StateMachineBuilder { Define states, events and side effects: ```swift -enum State: StateMachineHashable { +@StateMachineHashable +enum State { case solid, liquid, gas } -enum Event: StateMachineHashable { +@StateMachineHashable +enum Event { case melt, freeze, vaporize, condense } @@ -167,12 +169,19 @@ expect(transition).to(equal( expect(logger).to(log(Message.melted)) ``` -### Swift Enumerations with Associated Values +
+ +

Pre-Swift 5.9 Compatibility

-Due to Swift enumerations (as opposed to sealed classes in Kotlin), -any `State` or `Event` enumeration defined with associated values will require [boilerplate implementation](https://github.com/Tinder/StateMachine/blob/c5c8155d55db5799190d9a06fbc31263c76c80b6/Swift/Tests/StateMachineTests/StateMachine_Turnstile_Tests.swift#L198-L260) for `StateMachineHashable` conformance. +This information is only applicable to Swift versions older than `5.9`: -The easiest way to create this boilerplate is by using the [Sourcery](https://github.com/krzysztofzablocki/Sourcery) Swift code generator along with the [AutoStateMachineHashable stencil template](https://github.com/Tinder/StateMachine/blob/main/Swift/Resources/AutoStateMachineHashable.stencil) provided in this repository. Once the codegen is setup and configured, adopt `AutoStateMachineHashable` instead of `StateMachineHashable` for the `State` and/or `Event` enumerations. +> ### Swift Enumerations with Associated Values +> +> Due to Swift enumerations (as opposed to sealed classes in Kotlin), any `State` or `Event` enumeration defined with associated values will require [boilerplate implementation](https://github.com/Tinder/StateMachine/blob/c5c8155d55db5799190d9a06fbc31263c76c80b6/Swift/Tests/StateMachineTests/StateMachine_Turnstile_Tests.swift#L198-L260) for `StateMachineHashable` conformance. +> +> The easiest way to create this boilerplate is by using the [Sourcery](https://github.com/krzysztofzablocki/Sourcery) Swift code generator along with the [AutoStateMachineHashable stencil template](https://github.com/Tinder/StateMachine/blob/main/Swift/Resources/AutoStateMachineHashable.stencil) provided in this repository. Once the codegen is setup and configured, adopt `AutoStateMachineHashable` instead of `StateMachineHashable` for the `State` and/or `Event` enumerations. + +
## Examples @@ -231,7 +240,7 @@ pod 'StateMachine', :git => 'https://github.com/Tinder/StateMachine.git' Thanks to [@nvinayshetty](https://github.com/nvinayshetty), you can visualize your state machines right in the IDE using the [State Arts](https://github.com/nvinayshetty/StateArts) Intellij [plugin](https://plugins.jetbrains.com/plugin/12193-state-art). ## License -~~~ +``` Copyright (c) 2018, Match Group, LLC All rights reserved. @@ -256,4 +265,4 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -~~~ +``` diff --git a/Swift/Sources/StateMachine/Macros.swift b/Swift/Sources/StateMachine/Macros.swift new file mode 100644 index 0000000..4ddbf48 --- /dev/null +++ b/Swift/Sources/StateMachine/Macros.swift @@ -0,0 +1,10 @@ +// +// Copyright (c) 2024, Match Group, LLC +// BSD License, see LICENSE file for details +// + +@attached(extension, + conformances: StateMachineHashable, + names: named(hashableIdentifier), named(HashableIdentifier), named(associatedValue)) +public macro StateMachineHashable() = #externalMacro(module: "StateMachineMacros", + type: "StateMachineHashableMacro") diff --git a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift new file mode 100644 index 0000000..079c2ba --- /dev/null +++ b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift @@ -0,0 +1,93 @@ +// +// Copyright (c) 2024, Match Group, LLC +// BSD License, see LICENSE file for details +// + +import SwiftSyntax +import SwiftSyntaxMacros + +public struct StateMachineHashableMacro: 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] { + + guard let enumDecl: EnumDeclSyntax = declaration.as(EnumDeclSyntax.self) + else { throw StateMachineHashableMacroError.typeMustBeEnum } + + let elements: [EnumCaseElementSyntax] = enumDecl + .memberBlock + .members + .compactMap { $0.as(MemberBlockItemSyntax.self) } + .map(\.decl) + .compactMap { $0.as(EnumCaseDeclSyntax.self) } + .flatMap(\.elements) + + guard !elements.isEmpty + else { throw StateMachineHashableMacroError.enumMustHaveCases } + + let enumCases: [String] = elements + .map(\.name.text) + .map { "case \($0)" } + + let hashableIdentifierCases: [String] = elements + .map(\.name.text) + .map { "case .\($0):\nreturn .\($0)" } + + var associatedValueCases: [String] = [] + for element: EnumCaseElementSyntax in elements { + if let parameters: EnumCaseParameterListSyntax = element.parameterClause?.parameters, !parameters.isEmpty { + if parameters.count > 1 { + let associatedValues: String = (1...parameters.count) + .map { "value\($0)" } + .joined(separator: ", ") + let `case`: String = """ + case let .\(element.name.text)(\(associatedValues)): + return (\(associatedValues)) + """ + associatedValueCases.append(`case`) + } else { + let `case`: String = """ + case let .\(element.name.text)(value): + return (value) + """ + associatedValueCases.append(`case`) + } + } else { + let `case`: String = """ + case .\(element.name.text): + return () + """ + associatedValueCases.append(`case`) + } + } + + let decl: DeclSyntax = """ + extension \(type): StateMachineHashable { + + enum HashableIdentifier { + + \(raw: enumCases.joined(separator: "\n")) + } + + var hashableIdentifier: HashableIdentifier { + switch self { + \(raw: hashableIdentifierCases.joined(separator: "\n")) + } + } + + var associatedValue: Any { + switch self { + \(raw: associatedValueCases.joined(separator: "\n")) + } + } + } + """ + + return decl.as(ExtensionDeclSyntax.self).flatMap { [$0] } ?? [] + } +} diff --git a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift new file mode 100644 index 0000000..612935c --- /dev/null +++ b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift @@ -0,0 +1,19 @@ +// +// Copyright (c) 2024, Match Group, LLC +// BSD License, see LICENSE file for details +// + +public enum StateMachineHashableMacroError: Error, CustomStringConvertible { + + case typeMustBeEnum + case enumMustHaveCases + + public var description: String { + switch self { + case .typeMustBeEnum: + return "Type Must Be Enum" + case .enumMustHaveCases: + return "Enum Must Have Cases" + } + } +} diff --git a/Swift/Sources/StateMachineMacros/StateMachineMacros.swift b/Swift/Sources/StateMachineMacros/StateMachineMacros.swift new file mode 100644 index 0000000..b46bafe --- /dev/null +++ b/Swift/Sources/StateMachineMacros/StateMachineMacros.swift @@ -0,0 +1,17 @@ +// +// Copyright (c) 2024, Match Group, LLC +// BSD License, see LICENSE file for details +// + +#if canImport(SwiftCompilerPlugin) + +import SwiftCompilerPlugin +import SwiftSyntaxMacros + +@main +internal struct StateMachineMacros: CompilerPlugin { + + internal let providingMacros: [Macro.Type] = [StateMachineHashableMacro.self] +} + +#endif diff --git a/Swift/Tests/StateMachineTests/StateMachineMacrosTests.swift b/Swift/Tests/StateMachineTests/StateMachineMacrosTests.swift new file mode 100644 index 0000000..a19b300 --- /dev/null +++ b/Swift/Tests/StateMachineTests/StateMachineMacrosTests.swift @@ -0,0 +1,97 @@ +// +// Created by Christopher Fuller on 12/21/19. +// Copyright © 2019 Tinder. All rights reserved. +// + +#if canImport(StateMachineMacros) + +import StateMachineMacros +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class StateMachineMacrosTests: XCTestCase { + + private let macros: [String: Macro.Type] = ["StateMachineHashable": StateMachineHashableMacro.self] + + func testTypeMustBeEnumDiagnostic() { + assertMacroExpansion( + """ + @StateMachineHashable + struct Example {} + """, + expandedSource: """ + struct Example {} + """, + diagnostics: [DiagnosticSpec(message: "Type Must Be Enum", line: 1, column: 1)], + macros: macros + ) + } + + func testEnumMustHaveCasesDiagnostic() { + assertMacroExpansion( + """ + @StateMachineHashable + enum Example {} + """, + expandedSource: """ + enum Example {} + """, + diagnostics: [DiagnosticSpec(message: "Enum Must Have Cases", line: 1, column: 1)], + macros: macros + ) + } + + func testStateMachineHashableMacro() { + assertMacroExpansion( + """ + @StateMachineHashable + enum Example { + + case case0, case1(Any), case2(Any, Any) + } + """, + expandedSource: """ + enum Example { + + case case0, case1(Any), case2(Any, Any) + } + + extension Example: StateMachineHashable { + + enum HashableIdentifier { + + case case0 + case case1 + case case2 + } + + var hashableIdentifier: HashableIdentifier { + switch self { + case .case0: + return .case0 + case .case1: + return .case1 + case .case2: + return .case2 + } + } + + var associatedValue: Any { + switch self { + case .case0: + return () + case let .case1(value): + return (value) + case let .case2(value1, value2): + return (value1, value2) + } + } + } + """, + macros: macros + ) + } +} + +#endif diff --git a/Swift/Tests/StateMachineTests/StateMachineTests.swift b/Swift/Tests/StateMachineTests/StateMachineTests.swift index 9813b34..8003901 100644 --- a/Swift/Tests/StateMachineTests/StateMachineTests.swift +++ b/Swift/Tests/StateMachineTests/StateMachineTests.swift @@ -202,13 +202,13 @@ final class Logger { } } -func log(_ expectedMessages: String...) -> Predicate { +func log(_ expectedMessages: String...) -> Matcher { let expectedString: String = stringify(expectedMessages.joined(separator: "\\n")) - return Predicate { + return Matcher { let actualMessages: [String]? = try $0.evaluate()?.messages let actualString: String = stringify(actualMessages?.joined(separator: "\\n")) let message: ExpectationMessage = .expectedCustomValueTo("log <\(expectedString)>", actual: "<\(actualString)>") - return PredicateResult(bool: actualMessages == expectedMessages, message: message) + return MatcherResult(bool: actualMessages == expectedMessages, message: message) } } diff --git a/Swift/Tests/StateMachineTests/StateMachine_Matter_Tests.swift b/Swift/Tests/StateMachineTests/StateMachine_Matter_Tests.swift index d2d926e..1be563f 100644 --- a/Swift/Tests/StateMachineTests/StateMachine_Matter_Tests.swift +++ b/Swift/Tests/StateMachineTests/StateMachine_Matter_Tests.swift @@ -9,12 +9,14 @@ import XCTest final class StateMachine_Matter_Tests: XCTestCase, StateMachineBuilder { - enum State: StateMachineHashable { + @StateMachineHashable + enum State { case solid, liquid, gas } - enum Event: StateMachineHashable { + @StateMachineHashable + enum Event { case melt, freeze, vaporize, condense } diff --git a/Swift/Tests/StateMachineTests/StateMachine_Turnstile_Tests.swift b/Swift/Tests/StateMachineTests/StateMachine_Turnstile_Tests.swift index 38fbf33..1465d6e 100644 --- a/Swift/Tests/StateMachineTests/StateMachine_Turnstile_Tests.swift +++ b/Swift/Tests/StateMachineTests/StateMachine_Turnstile_Tests.swift @@ -14,11 +14,13 @@ final class StateMachine_Turnstile_Tests: XCTestCase, StateMachineBuilder { static let farePrice: Int = 50 } + @StateMachineHashable indirect enum State: Equatable { case locked(credit: Int), unlocked, broken(oldState: State) } + @StateMachineHashable enum Event: Equatable { case insertCoin(Int), admitPerson, machineDidFail, machineRepairDidComplete @@ -194,67 +196,3 @@ final class StateMachine_Turnstile_Tests: XCTestCase, StateMachineBuilder { sideEffect: nil))) } } - -extension StateMachine_Turnstile_Tests.State: StateMachineHashable { - - enum HashableIdentifier { - - case locked, unlocked, broken - } - - var hashableIdentifier: HashableIdentifier { - switch self { - case .locked: - return .locked - case .unlocked: - return .unlocked - case .broken: - return .broken - } - } - - var associatedValue: Any { - switch self { - case let .locked(credit): - return credit - case .unlocked: - return () - case let .broken(oldState): - return oldState - } - } -} - -extension StateMachine_Turnstile_Tests.Event: StateMachineHashable { - - enum HashableIdentifier { - - case insertCoin, admitPerson, machineDidFail, machineRepairDidComplete - } - - var hashableIdentifier: HashableIdentifier { - switch self { - case .insertCoin: - return .insertCoin - case .admitPerson: - return .admitPerson - case .machineDidFail: - return .machineDidFail - case .machineRepairDidComplete: - return .machineRepairDidComplete - } - } - - var associatedValue: Any { - switch self { - case let .insertCoin(value): - return value - case .admitPerson: - return () - case .machineDidFail: - return () - case .machineRepairDidComplete: - return () - } - } -} From bef545ffe630638b6bc39c3a9dead4e4935b0895 Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Wed, 28 Feb 2024 06:59:58 -0800 Subject: [PATCH 02/12] Update Swift workflow --- .github/workflows/swift.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 1f107d3..64df7de 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -7,14 +7,16 @@ on: branches: [ main ] env: - DEVELOPER_DIR: /Applications/Xcode_12.5.1.app/Contents/Developer + DEVELOPER_DIR: /Applications/Xcode_15.0.app/Contents/Developer jobs: - build_and_test: - runs-on: macos-11 + swift: + name: Swift + runs-on: macos-13 steps: - - uses: actions/checkout@v2 + - name: Checkout source + uses: actions/checkout@v3 - name: Build - run: swift build -v - - name: Run tests + run: swift build -v -Xswiftc -warnings-as-errors + - name: Test run: swift test -v From 104380d0620e133b4bca7321d676e7acb870d287 Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Sat, 2 Mar 2024 09:25:39 -0800 Subject: [PATCH 03/12] Fix expandable section in readme --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 709f072..01d3aa4 100755 --- a/README.md +++ b/README.md @@ -169,9 +169,13 @@ expect(transition).to(equal( expect(logger).to(log(Message.melted)) ``` +#### Pre-Swift 5.9 Compatibility +
-

Pre-Swift 5.9 Compatibility

+Expand + +
This information is only applicable to Swift versions older than `5.9`: From b0ce72d1909447b64118b2d3f11308ad5e637c3f Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Fri, 15 Mar 2024 08:59:14 -0700 Subject: [PATCH 04/12] Use initializers --- .../Macros/StateMachineHashableMacro.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift index 079c2ba..2876c24 100644 --- a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift +++ b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift @@ -16,15 +16,15 @@ public struct StateMachineHashableMacro: ExtensionMacro { in context: some MacroExpansionContext ) throws -> [ExtensionDeclSyntax] { - guard let enumDecl: EnumDeclSyntax = declaration.as(EnumDeclSyntax.self) + guard let enumDecl: EnumDeclSyntax = .init(declaration) else { throw StateMachineHashableMacroError.typeMustBeEnum } let elements: [EnumCaseElementSyntax] = enumDecl .memberBlock .members - .compactMap { $0.as(MemberBlockItemSyntax.self) } + .compactMap(MemberBlockItemSyntax.init) .map(\.decl) - .compactMap { $0.as(EnumCaseDeclSyntax.self) } + .compactMap(EnumCaseDeclSyntax.init) .flatMap(\.elements) guard !elements.isEmpty From 2f82a575509d402e660520cede2c7a8cab6c0c94 Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Fri, 15 Mar 2024 09:01:40 -0700 Subject: [PATCH 05/12] Throw error instead of returning empty array --- .../Macros/StateMachineHashableMacro.swift | 7 +++++-- .../Macros/StateMachineHashableMacroError.swift | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift index 2876c24..722ba08 100644 --- a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift +++ b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift @@ -66,7 +66,7 @@ public struct StateMachineHashableMacro: ExtensionMacro { } } - let decl: DeclSyntax = """ + let node: DeclSyntax = """ extension \(type): StateMachineHashable { enum HashableIdentifier { @@ -88,6 +88,9 @@ public struct StateMachineHashableMacro: ExtensionMacro { } """ - return decl.as(ExtensionDeclSyntax.self).flatMap { [$0] } ?? [] + guard let extensionDecl: ExtensionDeclSyntax = .init(node) + else { throw StateMachineHashableMacroError.invalidExtension } + + return [extensionDecl] } } diff --git a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift index 612935c..19a33fd 100644 --- a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift +++ b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift @@ -7,6 +7,7 @@ public enum StateMachineHashableMacroError: Error, CustomStringConvertible { case typeMustBeEnum case enumMustHaveCases + case invalidExtension public var description: String { switch self { @@ -14,6 +15,8 @@ public enum StateMachineHashableMacroError: Error, CustomStringConvertible { return "Type Must Be Enum" case .enumMustHaveCases: return "Enum Must Have Cases" + case .invalidExtension: + return "Invalid Extension" } } } From 25fce4d30a878c7cd488dafddcac195e43cf5065 Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Fri, 15 Mar 2024 09:02:50 -0700 Subject: [PATCH 06/12] Do not abbreviate enumeration --- .../Macros/StateMachineHashableMacro.swift | 4 ++-- .../Macros/StateMachineHashableMacroError.swift | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift index 722ba08..55e5277 100644 --- a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift +++ b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift @@ -17,7 +17,7 @@ public struct StateMachineHashableMacro: ExtensionMacro { ) throws -> [ExtensionDeclSyntax] { guard let enumDecl: EnumDeclSyntax = .init(declaration) - else { throw StateMachineHashableMacroError.typeMustBeEnum } + else { throw StateMachineHashableMacroError.typeMustBeEnumeration } let elements: [EnumCaseElementSyntax] = enumDecl .memberBlock @@ -28,7 +28,7 @@ public struct StateMachineHashableMacro: ExtensionMacro { .flatMap(\.elements) guard !elements.isEmpty - else { throw StateMachineHashableMacroError.enumMustHaveCases } + else { throw StateMachineHashableMacroError.enumerationMustHaveCases } let enumCases: [String] = elements .map(\.name.text) diff --git a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift index 19a33fd..6f69011 100644 --- a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift +++ b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift @@ -5,16 +5,16 @@ public enum StateMachineHashableMacroError: Error, CustomStringConvertible { - case typeMustBeEnum - case enumMustHaveCases + case typeMustBeEnumeration + case enumerationMustHaveCases case invalidExtension public var description: String { switch self { - case .typeMustBeEnum: - return "Type Must Be Enum" - case .enumMustHaveCases: - return "Enum Must Have Cases" + case .typeMustBeEnumeration: + return "Type Must Be Enumeration" + case .enumerationMustHaveCases: + return "Enumeration Must Have Cases" case .invalidExtension: return "Invalid Extension" } From fd2ee5c559c949001d4ca745817c7ec25d21c8ce Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Fri, 15 Mar 2024 09:07:09 -0700 Subject: [PATCH 07/12] Update file header comments --- .../StateMachineMacros/Macros/StateMachineHashableMacro.swift | 2 +- .../Macros/StateMachineHashableMacroError.swift | 2 +- Swift/Sources/StateMachineMacros/StateMachineMacros.swift | 2 +- Swift/Tests/StateMachineTests/StateMachineMacrosTests.swift | 4 ++-- Swift/Tests/StateMachineTests/StateMachineTests.swift | 4 ++-- Swift/Tests/StateMachineTests/StateMachine_Matter_Tests.swift | 4 ++-- .../StateMachineTests/StateMachine_Turnstile_Tests.swift | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift index 55e5277..694d254 100644 --- a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift +++ b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2024, Match Group, LLC +// Copyright (c) 2019, Match Group, LLC // BSD License, see LICENSE file for details // diff --git a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift index 6f69011..7f77d13 100644 --- a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift +++ b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2024, Match Group, LLC +// Copyright (c) 2019, Match Group, LLC // BSD License, see LICENSE file for details // diff --git a/Swift/Sources/StateMachineMacros/StateMachineMacros.swift b/Swift/Sources/StateMachineMacros/StateMachineMacros.swift index b46bafe..563ab1e 100644 --- a/Swift/Sources/StateMachineMacros/StateMachineMacros.swift +++ b/Swift/Sources/StateMachineMacros/StateMachineMacros.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2024, Match Group, LLC +// Copyright (c) 2019, Match Group, LLC // BSD License, see LICENSE file for details // diff --git a/Swift/Tests/StateMachineTests/StateMachineMacrosTests.swift b/Swift/Tests/StateMachineTests/StateMachineMacrosTests.swift index a19b300..67d3986 100644 --- a/Swift/Tests/StateMachineTests/StateMachineMacrosTests.swift +++ b/Swift/Tests/StateMachineTests/StateMachineMacrosTests.swift @@ -1,6 +1,6 @@ // -// Created by Christopher Fuller on 12/21/19. -// Copyright © 2019 Tinder. All rights reserved. +// Copyright (c) 2019, Match Group, LLC +// BSD License, see LICENSE file for details // #if canImport(StateMachineMacros) diff --git a/Swift/Tests/StateMachineTests/StateMachineTests.swift b/Swift/Tests/StateMachineTests/StateMachineTests.swift index 8003901..8e896ad 100644 --- a/Swift/Tests/StateMachineTests/StateMachineTests.swift +++ b/Swift/Tests/StateMachineTests/StateMachineTests.swift @@ -1,6 +1,6 @@ // -// Created by Christopher Fuller on 12/21/19. -// Copyright © 2019 Tinder. All rights reserved. +// Copyright (c) 2019, Match Group, LLC +// BSD License, see LICENSE file for details // import Nimble diff --git a/Swift/Tests/StateMachineTests/StateMachine_Matter_Tests.swift b/Swift/Tests/StateMachineTests/StateMachine_Matter_Tests.swift index 1be563f..a660f4c 100644 --- a/Swift/Tests/StateMachineTests/StateMachine_Matter_Tests.swift +++ b/Swift/Tests/StateMachineTests/StateMachine_Matter_Tests.swift @@ -1,6 +1,6 @@ // -// Created by Christopher Fuller on 12/21/19. -// Copyright © 2019 Tinder. All rights reserved. +// Copyright (c) 2019, Match Group, LLC +// BSD License, see LICENSE file for details // import Nimble diff --git a/Swift/Tests/StateMachineTests/StateMachine_Turnstile_Tests.swift b/Swift/Tests/StateMachineTests/StateMachine_Turnstile_Tests.swift index 1465d6e..3498472 100644 --- a/Swift/Tests/StateMachineTests/StateMachine_Turnstile_Tests.swift +++ b/Swift/Tests/StateMachineTests/StateMachine_Turnstile_Tests.swift @@ -1,6 +1,6 @@ // -// Created by Christopher Fuller on 12/21/19. -// Copyright © 2019 Tinder. All rights reserved. +// Copyright (c) 2019, Match Group, LLC +// BSD License, see LICENSE file for details // import Nimble From 7f490daec1921bac26cbf7aff774d7c68f0b9d6c Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Fri, 15 Mar 2024 09:10:28 -0700 Subject: [PATCH 08/12] Fix tests --- Swift/Tests/StateMachineTests/StateMachineMacrosTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Swift/Tests/StateMachineTests/StateMachineMacrosTests.swift b/Swift/Tests/StateMachineTests/StateMachineMacrosTests.swift index 67d3986..3dc8aab 100644 --- a/Swift/Tests/StateMachineTests/StateMachineMacrosTests.swift +++ b/Swift/Tests/StateMachineTests/StateMachineMacrosTests.swift @@ -23,7 +23,7 @@ final class StateMachineMacrosTests: XCTestCase { expandedSource: """ struct Example {} """, - diagnostics: [DiagnosticSpec(message: "Type Must Be Enum", line: 1, column: 1)], + diagnostics: [DiagnosticSpec(message: "Type Must Be Enumeration", line: 1, column: 1)], macros: macros ) } @@ -37,7 +37,7 @@ final class StateMachineMacrosTests: XCTestCase { expandedSource: """ enum Example {} """, - diagnostics: [DiagnosticSpec(message: "Enum Must Have Cases", line: 1, column: 1)], + diagnostics: [DiagnosticSpec(message: "Enumeration Must Have Cases", line: 1, column: 1)], macros: macros ) } From 992588b328f800e1000ef8f05bd2237b0833489d Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Fri, 15 Mar 2024 09:12:28 -0700 Subject: [PATCH 09/12] Utilize transform instead of for loop --- .../Macros/StateMachineHashableMacro.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift index 694d254..55ae977 100644 --- a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift +++ b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift @@ -38,8 +38,7 @@ public struct StateMachineHashableMacro: ExtensionMacro { .map(\.name.text) .map { "case .\($0):\nreturn .\($0)" } - var associatedValueCases: [String] = [] - for element: EnumCaseElementSyntax in elements { + let associatedValueCases: [String] = elements.map { element in if let parameters: EnumCaseParameterListSyntax = element.parameterClause?.parameters, !parameters.isEmpty { if parameters.count > 1 { let associatedValues: String = (1...parameters.count) @@ -49,20 +48,20 @@ public struct StateMachineHashableMacro: ExtensionMacro { case let .\(element.name.text)(\(associatedValues)): return (\(associatedValues)) """ - associatedValueCases.append(`case`) + return `case` } else { let `case`: String = """ case let .\(element.name.text)(value): return (value) """ - associatedValueCases.append(`case`) + return `case` } } else { let `case`: String = """ case .\(element.name.text): return () """ - associatedValueCases.append(`case`) + return `case` } } From 80654f3036c7fb61b2a7535870160d708dee1b0a Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Fri, 15 Mar 2024 09:26:28 -0700 Subject: [PATCH 10/12] Remove local variables --- .../Macros/StateMachineHashableMacro.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift index 55ae977..c5ac5de 100644 --- a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift +++ b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift @@ -44,24 +44,21 @@ public struct StateMachineHashableMacro: ExtensionMacro { let associatedValues: String = (1...parameters.count) .map { "value\($0)" } .joined(separator: ", ") - let `case`: String = """ + return """ case let .\(element.name.text)(\(associatedValues)): return (\(associatedValues)) """ - return `case` } else { - let `case`: String = """ + return """ case let .\(element.name.text)(value): return (value) """ - return `case` } } else { - let `case`: String = """ + return """ case .\(element.name.text): return () """ - return `case` } } From 3cc4f1aa1b52a2f78c5b90834064631014d8a522 Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Fri, 15 Mar 2024 09:37:01 -0700 Subject: [PATCH 11/12] Improve formatting --- .../Macros/StateMachineHashableMacro.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift index c5ac5de..0dc652a 100644 --- a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift +++ b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift @@ -45,19 +45,19 @@ public struct StateMachineHashableMacro: ExtensionMacro { .map { "value\($0)" } .joined(separator: ", ") return """ - case let .\(element.name.text)(\(associatedValues)): - return (\(associatedValues)) + case let .\(element.name.text)(\(associatedValues)): + return (\(associatedValues)) """ } else { return """ - case let .\(element.name.text)(value): - return (value) + case let .\(element.name.text)(value): + return (value) """ } } else { return """ - case .\(element.name.text): - return () + case .\(element.name.text): + return () """ } } @@ -72,13 +72,13 @@ public struct StateMachineHashableMacro: ExtensionMacro { var hashableIdentifier: HashableIdentifier { switch self { - \(raw: hashableIdentifierCases.joined(separator: "\n")) + \(raw: hashableIdentifierCases.joined(separator: "\n")) } } var associatedValue: Any { switch self { - \(raw: associatedValueCases.joined(separator: "\n")) + \(raw: associatedValueCases.joined(separator: "\n")) } } } From ada5b9d4b93adc77a8399a0aae07e7de3e52c289 Mon Sep 17 00:00:00 2001 From: Christopher Fuller Date: Fri, 15 Mar 2024 13:22:15 -0700 Subject: [PATCH 12/12] Use throwing initializer --- .../Macros/StateMachineHashableMacro.swift | 8 +++----- .../Macros/StateMachineHashableMacroError.swift | 3 --- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift index 0dc652a..1861113 100644 --- a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift +++ b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift @@ -4,6 +4,7 @@ // import SwiftSyntax +import SwiftSyntaxBuilder import SwiftSyntaxMacros public struct StateMachineHashableMacro: ExtensionMacro { @@ -62,7 +63,7 @@ public struct StateMachineHashableMacro: ExtensionMacro { } } - let node: DeclSyntax = """ + let node: SyntaxNodeString = """ extension \(type): StateMachineHashable { enum HashableIdentifier { @@ -84,9 +85,6 @@ public struct StateMachineHashableMacro: ExtensionMacro { } """ - guard let extensionDecl: ExtensionDeclSyntax = .init(node) - else { throw StateMachineHashableMacroError.invalidExtension } - - return [extensionDecl] + return try [ExtensionDeclSyntax(node)] } } diff --git a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift index 7f77d13..f1e5917 100644 --- a/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift +++ b/Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift @@ -7,7 +7,6 @@ public enum StateMachineHashableMacroError: Error, CustomStringConvertible { case typeMustBeEnumeration case enumerationMustHaveCases - case invalidExtension public var description: String { switch self { @@ -15,8 +14,6 @@ public enum StateMachineHashableMacroError: Error, CustomStringConvertible { return "Type Must Be Enumeration" case .enumerationMustHaveCases: return "Enumeration Must Have Cases" - case .invalidExtension: - return "Invalid Extension" } } }