Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancements to Macro Examples: Unit Tests and Code Organization #2163

Merged
merged 5 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
kimdv marked this conversation as resolved.
Show resolved Hide resolved
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())
ahoppen marked this conversation as resolved.
Show resolved Hide resolved
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