From ab14d160c089b34859f281d2ab053880a6cbd440 Mon Sep 17 00:00:00 2001 From: Ugo Date: Thu, 7 Mar 2024 13:50:47 +0200 Subject: [PATCH] Initial Commit --- .gitignore | 8 +++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 +++ Package.swift | 52 +++++++++++++++++++ Sources/CopyableMacro/CopyableMacro.swift | 11 ++++ Sources/CopyableMacroClient/main.swift | 8 +++ .../CopyableMacroMacro.swift | 33 ++++++++++++ .../CopyableMacroTests.swift | 46 ++++++++++++++++ 7 files changed, 166 insertions(+) create mode 100644 .gitignore create mode 100644 .swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 Package.swift create mode 100644 Sources/CopyableMacro/CopyableMacro.swift create mode 100644 Sources/CopyableMacroClient/main.swift create mode 100644 Sources/CopyableMacroMacros/CopyableMacroMacro.swift create mode 100644 Tests/CopyableMacroTests/CopyableMacroTests.swift diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..0c4aab6 --- /dev/null +++ b/Package.swift @@ -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: "CopyableMacro", + 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: "CopyableMacro", + targets: ["CopyableMacro"] + ), + .executable( + name: "CopyableMacroClient", + targets: ["CopyableMacroClient"] + ), + ], + 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: "CopyableMacroMacros", + 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: "CopyableMacro", dependencies: ["CopyableMacroMacros"]), + + // A client of the library, which is able to use the macro in its own code. + .executableTarget(name: "CopyableMacroClient", dependencies: ["CopyableMacro"]), + + // A test target used to develop the macro implementation. + .testTarget( + name: "CopyableMacroTests", + dependencies: [ + "CopyableMacroMacros", + .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"), + ] + ), + ] +) diff --git a/Sources/CopyableMacro/CopyableMacro.swift b/Sources/CopyableMacro/CopyableMacro.swift new file mode 100644 index 0000000..a394735 --- /dev/null +++ b/Sources/CopyableMacro/CopyableMacro.swift @@ -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")`. +@freestanding(expression) +public macro stringify(_ value: T) -> (T, String) = #externalMacro(module: "CopyableMacroMacros", type: "StringifyMacro") \ No newline at end of file diff --git a/Sources/CopyableMacroClient/main.swift b/Sources/CopyableMacroClient/main.swift new file mode 100644 index 0000000..6b0b2ba --- /dev/null +++ b/Sources/CopyableMacroClient/main.swift @@ -0,0 +1,8 @@ +import CopyableMacro + +let a = 17 +let b = 25 + +let (result, code) = #stringify(a + b) + +print("The value \(result) was produced by the code \"\(code)\"") diff --git a/Sources/CopyableMacroMacros/CopyableMacroMacro.swift b/Sources/CopyableMacroMacros/CopyableMacroMacro.swift new file mode 100644 index 0000000..4859e60 --- /dev/null +++ b/Sources/CopyableMacroMacros/CopyableMacroMacro.swift @@ -0,0 +1,33 @@ +import SwiftCompilerPlugin +import SwiftSyntax +import SwiftSyntaxBuilder +import SwiftSyntaxMacros + +/// Implementation of the `stringify` macro, which takes an expression +/// of any type and produces a tuple containing the value of that expression +/// and the source code that produced the value. For example +/// +/// #stringify(x + y) +/// +/// will expand to +/// +/// (x + y, "x + y") +public struct StringifyMacro: ExpressionMacro { + public static func expansion( + of node: some FreestandingMacroExpansionSyntax, + in context: some MacroExpansionContext + ) -> ExprSyntax { + guard let argument = node.argumentList.first?.expression else { + fatalError("compiler bug: the macro does not have any arguments") + } + + return "(\(argument), \(literal: argument.description))" + } +} + +@main +struct CopyableMacroPlugin: CompilerPlugin { + let providingMacros: [Macro.Type] = [ + StringifyMacro.self, + ] +} diff --git a/Tests/CopyableMacroTests/CopyableMacroTests.swift b/Tests/CopyableMacroTests/CopyableMacroTests.swift new file mode 100644 index 0000000..e98061b --- /dev/null +++ b/Tests/CopyableMacroTests/CopyableMacroTests.swift @@ -0,0 +1,46 @@ +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +// Macro implementations build for the host, so the corresponding module is not available when cross-compiling. Cross-compiled tests may still make use of the macro itself in end-to-end tests. +#if canImport(CopyableMacroMacros) +import CopyableMacroMacros + +let testMacros: [String: Macro.Type] = [ + "stringify": StringifyMacro.self, +] +#endif + +final class CopyableMacroTests: XCTestCase { + func testMacro() throws { + #if canImport(CopyableMacroMacros) + assertMacroExpansion( + """ + #stringify(a + b) + """, + expandedSource: """ + (a + b, "a + b") + """, + macros: testMacros + ) + #else + throw XCTSkip("macros are only supported when running tests for the host platform") + #endif + } + + func testMacroWithStringLiteral() throws { + #if canImport(CopyableMacroMacros) + assertMacroExpansion( + #""" + #stringify("Hello, \(name)") + """#, + expandedSource: #""" + ("Hello, \(name)", #""Hello, \(name)""#) + """#, + macros: testMacros + ) + #else + throw XCTSkip("macros are only supported when running tests for the host platform") + #endif + } +}