-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 9809ce6
Showing
9 changed files
with
598 additions
and
0 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 |
---|---|---|
@@ -0,0 +1,8 @@ | ||
.DS_Store | ||
/.build | ||
/Packages | ||
xcuserdata/ | ||
DerivedData/ | ||
.swiftpm/configuration/registries.json | ||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata | ||
.netrc |
8 changes: 8 additions & 0 deletions
8
.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
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,8 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
<plist version="1.0"> | ||
<dict> | ||
<key>IDEDidComputeMac32BitWarning</key> | ||
<true/> | ||
</dict> | ||
</plist> |
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,14 @@ | ||
{ | ||
"pins" : [ | ||
{ | ||
"identity" : "swift-syntax", | ||
"kind" : "remoteSourceControl", | ||
"location" : "https://github.com/apple/swift-syntax.git", | ||
"state" : { | ||
"revision" : "ffa3cd6fc2aa62adbedd31d3efaf7c0d86a9f029", | ||
"version" : "509.0.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,52 @@ | ||
// swift-tools-version: 5.9 | ||
// The swift-tools-version declares the minimum version of Swift required to build this package. | ||
|
||
import PackageDescription | ||
import CompilerPluginSupport | ||
|
||
let package = Package( | ||
name: "ThrowPublisher", | ||
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)], | ||
products: [ | ||
// Products define the executables and libraries a package produces, making them visible to other packages. | ||
.library( | ||
name: "ThrowPublisher", | ||
targets: ["ThrowPublisher"] | ||
), | ||
.executable( | ||
name: "ThrowPublisherClient", | ||
targets: ["ThrowPublisherClient"] | ||
), | ||
], | ||
dependencies: [ | ||
// Depend on the Swift 5.9 release of SwiftSyntax | ||
.package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0"), | ||
], | ||
targets: [ | ||
// Targets are the basic building blocks of a package, defining a module or a test suite. | ||
// Targets can depend on other targets in this package and products from dependencies. | ||
// Macro implementation that performs the source transformation of a macro. | ||
.macro( | ||
name: "ThrowPublisherMacros", | ||
dependencies: [ | ||
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"), | ||
.product(name: "SwiftCompilerPlugin", package: "swift-syntax") | ||
] | ||
), | ||
|
||
// Library that exposes a macro as part of its API, which is used in client programs. | ||
.target(name: "ThrowPublisher", dependencies: ["ThrowPublisherMacros"]), | ||
|
||
// A client of the library, which is able to use the macro in its own code. | ||
.executableTarget(name: "ThrowPublisherClient", dependencies: ["ThrowPublisher"]), | ||
|
||
// A test target used to develop the macro implementation. | ||
.testTarget( | ||
name: "ThrowPublisherTests", | ||
dependencies: [ | ||
"ThrowPublisherMacros", | ||
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), | ||
] | ||
), | ||
] | ||
) |
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 @@ | ||
# ThrowPublisher |
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,11 @@ | ||
// The Swift Programming Language | ||
// https://docs.swift.org/swift-book | ||
|
||
/// A macro that produces both a value and a string containing the | ||
/// source code that generated the value. For example, | ||
/// | ||
/// #stringify(x + y) | ||
/// | ||
/// produces a tuple `(x + y, "x + y")`. | ||
@attached(peer, names: arbitrary) | ||
public macro ThrowPublisher() = #externalMacro(module: "ThrowPublisherMacros", type: "ThrowPublisherMacro") |
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,52 @@ | ||
import ThrowPublisher | ||
import Combine | ||
import Foundation | ||
|
||
var cancellables = Set<AnyCancellable>() | ||
|
||
struct MyStruct { | ||
@ThrowPublisher | ||
func doSomething(arg: String) throws -> String { | ||
"Something" | ||
} | ||
|
||
@ThrowPublisher | ||
func doSomething(arg: (String) -> String) throws -> String { | ||
arg("Something") | ||
} | ||
|
||
@ThrowPublisher | ||
func doSomething(_ arg: String) throws -> String { | ||
"Something" | ||
} | ||
|
||
@ThrowPublisher | ||
func doSomething() throws -> Void { | ||
print("Something") | ||
} | ||
|
||
@ThrowPublisher | ||
func doSomething(arg: String?) throws -> String? { | ||
nil | ||
} | ||
|
||
@ThrowPublisher | ||
func doSomething<T>(arg: T) throws -> String where T: Equatable { | ||
"Something" | ||
} | ||
} | ||
|
||
let myStruct = MyStruct() | ||
|
||
myStruct.doSomething_publisher() | ||
.sink { completion in | ||
switch completion { | ||
case .finished: | ||
print("finished") | ||
case .failure(let error): | ||
print(error.localizedDescription) | ||
} | ||
} receiveValue: { value in | ||
print(value) | ||
}.store(in: &cancellables) | ||
|
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,130 @@ | ||
import SwiftCompilerPlugin | ||
import SwiftSyntax | ||
import SwiftSyntaxBuilder | ||
import SwiftSyntaxMacros | ||
import Foundation | ||
|
||
enum ThrowPublisherError: Error, CustomStringConvertible { | ||
case notFunction | ||
case wildcard | ||
case identifierType | ||
case asyncSpecifier | ||
case noThrow | ||
case unsupportedWhereClause | ||
|
||
var description: String { | ||
switch self { | ||
case .notFunction: | ||
return "Declaration must be function." | ||
case .identifierType: | ||
return "Parameter type must be identifier type." | ||
case .wildcard: | ||
return "Could not get the name of wildcard(_) parameter." | ||
case .asyncSpecifier: | ||
return "ThrowPublisher doesn't support async specifier." | ||
case .unsupportedWhereClause: | ||
return "Unsupported where clause." | ||
case .noThrow: | ||
return "Function doesn't throw." | ||
} | ||
} | ||
} | ||
|
||
public struct ThrowPublisherMacro: PeerMacro { | ||
|
||
public static func expansion(of node: AttributeSyntax, providingPeersOf declaration: some DeclSyntaxProtocol, in context: some MacroExpansionContext) throws -> [DeclSyntax] { | ||
guard let functionDecl = declaration.as(FunctionDeclSyntax.self) else { | ||
throw ThrowPublisherError.notFunction | ||
} | ||
guard functionDecl.signature.effectSpecifiers?.asyncSpecifier == nil else { | ||
throw ThrowPublisherError.asyncSpecifier | ||
} | ||
guard functionDecl.signature.effectSpecifiers?.throwsSpecifier != nil else { | ||
throw ThrowPublisherError.noThrow | ||
} | ||
|
||
let returnType: String | ||
if let name = functionDecl.signature.returnClause?.type.as(IdentifierTypeSyntax.self)?.name { | ||
returnType = name.text | ||
} else if let optionalTypeSyntax = functionDecl.signature.returnClause?.type.as(OptionalTypeSyntax.self), | ||
let name = optionalTypeSyntax.wrappedType.as(IdentifierTypeSyntax.self)?.name { | ||
returnType = "\(name.text)?" | ||
} else { | ||
returnType = "Void" | ||
} | ||
|
||
let functionName = functionDecl.name.text | ||
var newFunctionName = "\(functionName)_publisher" | ||
|
||
if let genericParameterClause = functionDecl.genericParameterClause { | ||
let generics = genericParameterClause.parameters.map { | ||
$0.name.text | ||
}.joined(separator: ", ") | ||
newFunctionName += "<\(generics)>" | ||
} | ||
|
||
var genericPart: String? | ||
if let genericWhereClause = functionDecl.genericWhereClause { | ||
let generics = try genericWhereClause.requirements.map { | ||
guard let conformanceRequirementSyntax = $0.requirement.as(ConformanceRequirementSyntax.self), | ||
let leftType = conformanceRequirementSyntax.leftType.as(IdentifierTypeSyntax.self), | ||
let rightType = conformanceRequirementSyntax.rightType.as(IdentifierTypeSyntax.self)else { throw ThrowPublisherError.unsupportedWhereClause } | ||
return "\(leftType.name.text)\(conformanceRequirementSyntax.colon.text) \(rightType.name.text)" | ||
}.joined(separator: ", ") | ||
genericPart = "where \(generics)" | ||
} | ||
|
||
let parameters = "\(functionDecl.signature.parameterClause)" | ||
let parametersWithCall = try functionDecl.signature.parameterClause.parameters.map { | ||
if $0.firstName.text == "_" { | ||
guard let secondName = $0.secondName else { throw ThrowPublisherError.wildcard } | ||
return secondName.text | ||
} | ||
return "\($0.firstName.text): \($0.firstName.text)" | ||
}.joined(separator: ", ") | ||
|
||
let resultString: String | ||
if returnType == "Void" { | ||
resultString = """ | ||
try \(functionName)(\(parametersWithCall)) | ||
return .success(()) | ||
""" | ||
} else { | ||
resultString = """ | ||
let result = try \(functionName)(\(parametersWithCall)) | ||
return .success(result) | ||
""" | ||
} | ||
|
||
var returnPart = "AnyPublisher<\(returnType), Error>" | ||
if let genericPart { | ||
returnPart += " \(genericPart)" | ||
} | ||
|
||
let hop = """ | ||
func \(newFunctionName)\(parameters)-> \(returnPart) { | ||
func getResult() -> Result<\(returnType), Error> { | ||
do { | ||
\(resultString) | ||
} catch { | ||
return .failure(error) | ||
} | ||
} | ||
return getResult() | ||
.publisher | ||
.eraseToAnyPublisher() | ||
} | ||
""" | ||
|
||
return [""" | ||
\(raw: hop) | ||
""" | ||
] | ||
} | ||
} | ||
@main | ||
struct ThrowPublisherPlugin: CompilerPlugin { | ||
let providingMacros: [Macro.Type] = [ | ||
ThrowPublisherMacro.self, | ||
] | ||
} |
Oops, something went wrong.