-
Notifications
You must be signed in to change notification settings - Fork 130
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Swift macro (requires Swift v5.9)
- Loading branch information
1 parent
0f79642
commit 74a003f
Showing
11 changed files
with
356 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"), | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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") |
93 changes: 93 additions & 0 deletions
93
Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacro.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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] } ?? [] | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
Swift/Sources/StateMachineMacros/Macros/StateMachineHashableMacroError.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.