Skip to content

Commit

Permalink
Merge pull request #2163 from Matejkob/macro-examples-unit-tests
Browse files Browse the repository at this point in the history
Enhancements to Macro Examples: Unit Tests and Code Organization
  • Loading branch information
ahoppen committed Sep 20, 2023
2 parents 2c847f8 + 919ae85 commit 806c473
Show file tree
Hide file tree
Showing 45 changed files with 2,018 additions and 624 deletions.
4 changes: 3 additions & 1 deletion Examples/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ let package = Package(
.testTarget(
name: "MacroExamplesImplementationTests",
dependencies: [
"MacroExamplesImplementation"
"MacroExamplesImplementation",
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
],
path: "Tests/MacroExamples/Implementation"
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,14 @@ public struct DictionaryStoragePropertyMacro: AccessorMacro {

return [
"""
get {
_storage[\(literal: identifier.text), default: \(defaultValue)] as! \(type)
}
get {
_storage[\(literal: identifier.text), default: \(defaultValue)] as! \(type)
}
""",
"""
set {
_storage[\(literal: identifier.text)] = newValue
}
set {
_storage[\(literal: identifier.text)] = newValue
}
""",
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ import SwiftSyntaxMacros
/// Implementation of the `#fontLiteral` macro, which is similar in spirit
/// to the built-in expressions `#colorLiteral`, `#imageLiteral`, etc., but in
/// a small macro.
public struct FontLiteralMacro: ExpressionMacro {
public enum FontLiteralMacro: ExpressionMacro {
public static func expansion(
of macro: some FreestandingMacroExpansionSyntax,
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> ExprSyntax {
) throws -> ExprSyntax {
let argList = replaceFirstLabel(
of: macro.arguments,
of: node.arguments,
with: "fontLiteralName"
)
return ".init(\(argList))"
Expand All @@ -40,6 +40,9 @@ private func replaceFirstLabel(
}

var tuple = tuple
tuple[tuple.startIndex] = firstElement.with(\.label, .identifier(newLabel))
tuple[tuple.startIndex] =
firstElement
.with(\.label, .identifier(newLabel))
.with(\.colon, .colonToken())
return tuple
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import SwiftSyntaxMacros
/// will expand to
///
/// (x + y, "x + y")
public struct StringifyMacro: ExpressionMacro {
public enum StringifyMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ import SwiftSyntaxMacros

/// Creates a non-optional URL from a static string. The string is checked to
/// be valid during compile time.
public struct URLMacro: ExpressionMacro {
public enum URLMacro: ExpressionMacro {
public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {

guard let argument = node.arguments.first?.expression,
let segments = argument.as(StringLiteralExprSyntax.self)?.segments,
segments.count == 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ import SwiftSyntaxMacros

/// Implementation of the `myWarning` macro, which mimics the behavior of the
/// built-in `#warning`.
public struct WarningMacro: ExpressionMacro {
public enum WarningMacro: ExpressionMacro {
public static func expansion(
of macro: some FreestandingMacroExpansionSyntax,
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) throws -> ExprSyntax {
guard let firstElement = macro.arguments.first,
guard let firstElement = node.arguments.first,
let stringLiteral = firstElement.expression
.as(StringLiteralExprSyntax.self),
stringLiteral.segments.count == 1,
Expand All @@ -32,10 +32,10 @@ public struct WarningMacro: ExpressionMacro {

context.diagnose(
Diagnostic(
node: Syntax(macro),
node: Syntax(node),
message: SimpleDiagnosticMessage(
message: messageString.content.description,
diagnosticID: MessageID(domain: "test", id: "error"),
diagnosticID: MessageID(domain: "test123", id: "error"),
severity: .warning
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,11 @@
import SwiftSyntax
import SwiftSyntaxMacros

extension TokenSyntax {
fileprivate var initialUppercased: String {
let name = self.text
guard let initial = name.first else {
return name
}

return "\(initial.uppercased())\(name.dropFirst())"
}
}

public struct CaseDetectionMacro: MemberMacro {
public static func expansion<
Declaration: DeclGroupSyntax,
Context: MacroExpansionContext
>(
public enum CaseDetectionMacro: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: Declaration,
in context: Context
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
declaration.memberBlock.members
.compactMap { $0.decl.as(EnumCaseDeclSyntax.self) }
Expand All @@ -50,3 +36,14 @@ public struct CaseDetectionMacro: MemberMacro {
}
}
}

extension TokenSyntax {
fileprivate var initialUppercased: String {
let name = self.text
guard let initial = name.first else {
return name
}

return "\(initial.uppercased())\(name.dropFirst())"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@
import SwiftSyntax
import SwiftSyntaxMacros

public struct CustomCodable: MemberMacro {

public enum CustomCodable: MemberMacro {
public static func expansion(
of node: AttributeSyntax,
providingMembersOf declaration: some DeclGroupSyntax,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {

let memberList = declaration.memberBlock.members

let cases = memberList.compactMap({ member -> String? in
Expand All @@ -46,15 +44,22 @@ public struct CustomCodable: MemberMacro {
})

let codingKeys: DeclSyntax = """
enum CodingKeys: String, CodingKey {
\(raw: cases.joined(separator: "\n"))
}
enum CodingKeys: String, CodingKey {
\(raw: cases.joined(separator: "\n"))
}
"""

return [
codingKeys
]
return [codingKeys]
}
}

public struct CodableKey: PeerMacro {
public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
// Does nothing, used only to decorate members with data
return []
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,36 +41,39 @@ public struct MetaEnumMacro {

func makeMetaEnum() -> DeclSyntax {
// FIXME: Why does this need to be a string to make trailing trivia work properly?
let caseDecls = childCases.map { childCase in
" case \(childCase.name)"
}.joined(separator: "\n")
let caseDecls =
childCases
.map { childCase in
" case \(childCase.name)"
}
.joined(separator: "\n")

return """
\(access)enum Meta {
\(access)enum Meta {
\(raw: caseDecls)
\(makeMetaInit())
}
}
"""
}

func makeMetaInit() -> DeclSyntax {
// FIXME: Why does this need to be a string to make trailing trivia work properly?
let caseStatements = childCases.map { childCase in
"""
case .\(childCase.name):
self = .\(childCase.name)
"""
}.joined(separator: "\n")
let caseStatements =
childCases
.map { childCase in
"""
case .\(childCase.name):
self = .\(childCase.name)
"""
}
.joined(separator: "\n")

return """
\(access)init(_ \(parentParamName): \(parentTypeName)) {
switch \(parentParamName) {
\(access)init(_ \(parentParamName): \(parentTypeName)) {
switch \(parentParamName) {
\(raw: caseStatements)
}
}
}
}
"""
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,12 @@ public struct AddAsyncMacro: PeerMacro {

let switchBody: ExprSyntax =
"""
switch returnValue {
case .success(let value):
continuation.resume(returning: value)
case .failure(let error):
continuation.resume(throwing: error)
}
switch returnValue {
case .success(let value):
continuation.resume(returning: value)
case .failure(let error):
continuation.resume(throwing: error)
}
"""

let newBody: ExprSyntax =
Expand All @@ -115,8 +115,7 @@ public struct AddAsyncMacro: PeerMacro {
\(raw: isResultReturn ? "try await withCheckedThrowingContinuation { continuation in" : "await withCheckedContinuation { continuation in")
\(raw: funcDecl.name)(\(raw: callArguments.joined(separator: ", "))) { \(raw: returnType != nil ? "returnValue in" : "")
\(raw: isResultReturn ? switchBody : "continuation.resume(returning: \(raw: returnType != nil ? "returnValue" : "()"))")
\(raw: isResultReturn ? switchBody : "continuation.resume(returning: \(raw: returnType != nil ? "returnValue" : "()"))")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,7 @@
//
//===----------------------------------------------------------------------===//

import Foundation

/// "Stringify" the provided value and produce a tuple that includes both the
/// original value as well as the source code that generated it.
@freestanding(expression) public macro stringify<T>(_ value: T) -> (T, String) = #externalMacro(module: "MacroExamplesImplementation", type: "StringifyMacro")

/// Macro that produces a warning on "+" operators within the expression, and
/// suggests changing them to "-".
@freestanding(expression) public macro addBlocker<T>(_ value: T) -> T = #externalMacro(module: "MacroExamplesImplementation", type: "AddBlocker")

/// Macro that produces a warning, as a replacement for the built-in
/// #warning("...").
@freestanding(expression) public macro myWarning(_ message: String) = #externalMacro(module: "MacroExamplesImplementation", type: "WarningMacro")

public enum FontWeight {
case thin
case normal
case medium
case semiBold
case bold
}

public protocol ExpressibleByFontLiteral {
init(fontLiteralName: String, size: Int, weight: FontWeight)
}

/// Font literal similar to, e.g., #colorLiteral.
@freestanding(expression) public macro fontLiteral<T>(name: String, size: Int, weight: FontWeight) -> T =
#externalMacro(module: "MacroExamplesImplementation", type: "FontLiteralMacro")
where T: ExpressibleByFontLiteral

/// Check if provided string literal is a valid URL and produce a non-optional
/// URL value. Emit error otherwise.
@freestanding(expression) public macro URL(_ stringLiteral: String) -> URL = #externalMacro(module: "MacroExamplesImplementation", type: "URLMacro")

/// Apply the specified attribute to each of the stored properties within the
/// type or member to which the macro is attached. The string can be
/// any attribute (without the `@`).
@attached(memberAttribute)
public macro wrapStoredProperties(_ attributeName: String) = #externalMacro(module: "MacroExamplesImplementation", type: "WrapStoredPropertiesMacro")
// MARK: - Dictionary Storage

/// Wrap up the stored properties of the given type in a dictionary,
/// turning them into computed properties.
Expand All @@ -66,6 +27,8 @@ public macro DictionaryStorage() = #externalMacro(module: "MacroExamplesImplemen
@attached(accessor)
public macro DictionaryStorageProperty() = #externalMacro(module: "MacroExamplesImplementation", type: "DictionaryStoragePropertyMacro")

// MARK: - Observable

public protocol Observable {}

public protocol Observer<Subject> {
Expand Down Expand Up @@ -108,29 +71,7 @@ public macro Observable() = #externalMacro(module: "MacroExamplesImplementation"
@attached(accessor)
public macro ObservableProperty() = #externalMacro(module: "MacroExamplesImplementation", type: "ObservablePropertyMacro")

/// Adds a "completionHandler" variant of an async function, which creates a new
/// task , calls thh original async function, and delivers its result to the completion
/// handler.
@attached(peer, names: overloaded)
public macro AddCompletionHandler() =
#externalMacro(module: "MacroExamplesImplementation", type: "AddCompletionHandlerMacro")

@attached(peer, names: overloaded)
public macro AddAsync() =
#externalMacro(module: "MacroExamplesImplementation", type: "AddAsyncMacro")

/// Add computed properties named `is<Case>` for each case element in the enum.
@attached(member, names: arbitrary)
public macro CaseDetection() = #externalMacro(module: "MacroExamplesImplementation", type: "CaseDetectionMacro")

@attached(member, names: named(Meta))
public macro MetaEnum() = #externalMacro(module: "MacroExamplesImplementation", type: "MetaEnumMacro")

@attached(peer)
public macro CodableKey(name: String) = #externalMacro(module: "MacroExamplesImplementation", type: "CodableKey")

@attached(member, names: named(CodingKeys))
public macro CustomCodable() = #externalMacro(module: "MacroExamplesImplementation", type: "CustomCodable")
// MARK: - Option Set

/// Create an option set from a struct that contains a nested `Options` enum.
///
Expand Down
Loading

0 comments on commit 806c473

Please sign in to comment.