From 6c0d1191c2a50bb578e8f9fc186a0145d0616ea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ba=CC=A8k?= Date: Wed, 6 Sep 2023 12:24:04 +0200 Subject: [PATCH 1/5] Categorize Macro Implementations by Macro Type This commit restructures the macro implementation examples into type-specific directories, streamlining the search process for newcomers and enhancing overall accessibility --- .../DictionaryIndirectionMacro.swift | 0 .../{ => ComplexMacros}/ObservableMacro.swift | 0 .../{ => ComplexMacros}/OptionSetMacro.swift | 0 .../{ => Expression}/AddBlocker.swift | 0 .../{ => Expression}/FontLiteralMacro.swift | 12 ++++--- .../{ => Expression}/StringifyMacro.swift | 2 +- .../{ => Expression}/URLMacro.swift | 3 +- .../{ => Expression}/WarningMacro.swift | 10 +++--- .../{ => Member}/CaseDetectionMacro.swift | 33 +++++++++---------- .../{ => Member}/CustomCodable.swift | 8 ++--- .../{ => Member}/MetaEnumMacro.swift | 0 .../{ => Member}/NewTypeMacro.swift | 0 .../WrapStoredPropertiesMacro.swift | 0 .../{ => Peer}/AddAsyncMacro.swift | 0 .../AddCompletionHandlerMacro.swift | 0 .../{ => Peer}/CodableKey.swift | 0 16 files changed, 31 insertions(+), 37 deletions(-) rename Examples/Sources/MacroExamples/Implementation/{ => ComplexMacros}/DictionaryIndirectionMacro.swift (100%) rename Examples/Sources/MacroExamples/Implementation/{ => ComplexMacros}/ObservableMacro.swift (100%) rename Examples/Sources/MacroExamples/Implementation/{ => ComplexMacros}/OptionSetMacro.swift (100%) rename Examples/Sources/MacroExamples/Implementation/{ => Expression}/AddBlocker.swift (100%) rename Examples/Sources/MacroExamples/Implementation/{ => Expression}/FontLiteralMacro.swift (82%) rename Examples/Sources/MacroExamples/Implementation/{ => Expression}/StringifyMacro.swift (96%) rename Examples/Sources/MacroExamples/Implementation/{ => Expression}/URLMacro.swift (96%) rename Examples/Sources/MacroExamples/Implementation/{ => Expression}/WarningMacro.swift (84%) rename Examples/Sources/MacroExamples/Implementation/{ => Member}/CaseDetectionMacro.swift (84%) rename Examples/Sources/MacroExamples/Implementation/{ => Member}/CustomCodable.swift (95%) rename Examples/Sources/MacroExamples/Implementation/{ => Member}/MetaEnumMacro.swift (100%) rename Examples/Sources/MacroExamples/Implementation/{ => Member}/NewTypeMacro.swift (100%) rename Examples/Sources/MacroExamples/Implementation/{ => MemberAttribute}/WrapStoredPropertiesMacro.swift (100%) rename Examples/Sources/MacroExamples/Implementation/{ => Peer}/AddAsyncMacro.swift (100%) rename Examples/Sources/MacroExamples/Implementation/{ => Peer}/AddCompletionHandlerMacro.swift (100%) rename Examples/Sources/MacroExamples/Implementation/{ => Peer}/CodableKey.swift (100%) diff --git a/Examples/Sources/MacroExamples/Implementation/DictionaryIndirectionMacro.swift b/Examples/Sources/MacroExamples/Implementation/ComplexMacros/DictionaryIndirectionMacro.swift similarity index 100% rename from Examples/Sources/MacroExamples/Implementation/DictionaryIndirectionMacro.swift rename to Examples/Sources/MacroExamples/Implementation/ComplexMacros/DictionaryIndirectionMacro.swift diff --git a/Examples/Sources/MacroExamples/Implementation/ObservableMacro.swift b/Examples/Sources/MacroExamples/Implementation/ComplexMacros/ObservableMacro.swift similarity index 100% rename from Examples/Sources/MacroExamples/Implementation/ObservableMacro.swift rename to Examples/Sources/MacroExamples/Implementation/ComplexMacros/ObservableMacro.swift diff --git a/Examples/Sources/MacroExamples/Implementation/OptionSetMacro.swift b/Examples/Sources/MacroExamples/Implementation/ComplexMacros/OptionSetMacro.swift similarity index 100% rename from Examples/Sources/MacroExamples/Implementation/OptionSetMacro.swift rename to Examples/Sources/MacroExamples/Implementation/ComplexMacros/OptionSetMacro.swift diff --git a/Examples/Sources/MacroExamples/Implementation/AddBlocker.swift b/Examples/Sources/MacroExamples/Implementation/Expression/AddBlocker.swift similarity index 100% rename from Examples/Sources/MacroExamples/Implementation/AddBlocker.swift rename to Examples/Sources/MacroExamples/Implementation/Expression/AddBlocker.swift diff --git a/Examples/Sources/MacroExamples/Implementation/FontLiteralMacro.swift b/Examples/Sources/MacroExamples/Implementation/Expression/FontLiteralMacro.swift similarity index 82% rename from Examples/Sources/MacroExamples/Implementation/FontLiteralMacro.swift rename to Examples/Sources/MacroExamples/Implementation/Expression/FontLiteralMacro.swift index 9186114db20..a9e3d3f2cc3 100644 --- a/Examples/Sources/MacroExamples/Implementation/FontLiteralMacro.swift +++ b/Examples/Sources/MacroExamples/Implementation/Expression/FontLiteralMacro.swift @@ -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))" @@ -40,6 +40,8 @@ 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 } diff --git a/Examples/Sources/MacroExamples/Implementation/StringifyMacro.swift b/Examples/Sources/MacroExamples/Implementation/Expression/StringifyMacro.swift similarity index 96% rename from Examples/Sources/MacroExamples/Implementation/StringifyMacro.swift rename to Examples/Sources/MacroExamples/Implementation/Expression/StringifyMacro.swift index 5ffaf844ef0..5d0d7c8ef2e 100644 --- a/Examples/Sources/MacroExamples/Implementation/StringifyMacro.swift +++ b/Examples/Sources/MacroExamples/Implementation/Expression/StringifyMacro.swift @@ -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 diff --git a/Examples/Sources/MacroExamples/Implementation/URLMacro.swift b/Examples/Sources/MacroExamples/Implementation/Expression/URLMacro.swift similarity index 96% rename from Examples/Sources/MacroExamples/Implementation/URLMacro.swift rename to Examples/Sources/MacroExamples/Implementation/Expression/URLMacro.swift index 0aebda0311f..5b5ccf88951 100644 --- a/Examples/Sources/MacroExamples/Implementation/URLMacro.swift +++ b/Examples/Sources/MacroExamples/Implementation/Expression/URLMacro.swift @@ -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, diff --git a/Examples/Sources/MacroExamples/Implementation/WarningMacro.swift b/Examples/Sources/MacroExamples/Implementation/Expression/WarningMacro.swift similarity index 84% rename from Examples/Sources/MacroExamples/Implementation/WarningMacro.swift rename to Examples/Sources/MacroExamples/Implementation/Expression/WarningMacro.swift index 87db9064966..9f2163c04a1 100644 --- a/Examples/Sources/MacroExamples/Implementation/WarningMacro.swift +++ b/Examples/Sources/MacroExamples/Implementation/Expression/WarningMacro.swift @@ -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, @@ -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 ) ) diff --git a/Examples/Sources/MacroExamples/Implementation/CaseDetectionMacro.swift b/Examples/Sources/MacroExamples/Implementation/Member/CaseDetectionMacro.swift similarity index 84% rename from Examples/Sources/MacroExamples/Implementation/CaseDetectionMacro.swift rename to Examples/Sources/MacroExamples/Implementation/Member/CaseDetectionMacro.swift index 02dd0a01706..4e9e184e943 100644 --- a/Examples/Sources/MacroExamples/Implementation/CaseDetectionMacro.swift +++ b/Examples/Sources/MacroExamples/Implementation/Member/CaseDetectionMacro.swift @@ -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) } @@ -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())" + } +} diff --git a/Examples/Sources/MacroExamples/Implementation/CustomCodable.swift b/Examples/Sources/MacroExamples/Implementation/Member/CustomCodable.swift similarity index 95% rename from Examples/Sources/MacroExamples/Implementation/CustomCodable.swift rename to Examples/Sources/MacroExamples/Implementation/Member/CustomCodable.swift index e89b9316b93..242ebbe2b18 100644 --- a/Examples/Sources/MacroExamples/Implementation/CustomCodable.swift +++ b/Examples/Sources/MacroExamples/Implementation/Member/CustomCodable.swift @@ -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 @@ -53,8 +51,6 @@ public struct CustomCodable: MemberMacro { """ - return [ - codingKeys - ] + return [codingKeys] } } diff --git a/Examples/Sources/MacroExamples/Implementation/MetaEnumMacro.swift b/Examples/Sources/MacroExamples/Implementation/Member/MetaEnumMacro.swift similarity index 100% rename from Examples/Sources/MacroExamples/Implementation/MetaEnumMacro.swift rename to Examples/Sources/MacroExamples/Implementation/Member/MetaEnumMacro.swift diff --git a/Examples/Sources/MacroExamples/Implementation/NewTypeMacro.swift b/Examples/Sources/MacroExamples/Implementation/Member/NewTypeMacro.swift similarity index 100% rename from Examples/Sources/MacroExamples/Implementation/NewTypeMacro.swift rename to Examples/Sources/MacroExamples/Implementation/Member/NewTypeMacro.swift diff --git a/Examples/Sources/MacroExamples/Implementation/WrapStoredPropertiesMacro.swift b/Examples/Sources/MacroExamples/Implementation/MemberAttribute/WrapStoredPropertiesMacro.swift similarity index 100% rename from Examples/Sources/MacroExamples/Implementation/WrapStoredPropertiesMacro.swift rename to Examples/Sources/MacroExamples/Implementation/MemberAttribute/WrapStoredPropertiesMacro.swift diff --git a/Examples/Sources/MacroExamples/Implementation/AddAsyncMacro.swift b/Examples/Sources/MacroExamples/Implementation/Peer/AddAsyncMacro.swift similarity index 100% rename from Examples/Sources/MacroExamples/Implementation/AddAsyncMacro.swift rename to Examples/Sources/MacroExamples/Implementation/Peer/AddAsyncMacro.swift diff --git a/Examples/Sources/MacroExamples/Implementation/AddCompletionHandlerMacro.swift b/Examples/Sources/MacroExamples/Implementation/Peer/AddCompletionHandlerMacro.swift similarity index 100% rename from Examples/Sources/MacroExamples/Implementation/AddCompletionHandlerMacro.swift rename to Examples/Sources/MacroExamples/Implementation/Peer/AddCompletionHandlerMacro.swift diff --git a/Examples/Sources/MacroExamples/Implementation/CodableKey.swift b/Examples/Sources/MacroExamples/Implementation/Peer/CodableKey.swift similarity index 100% rename from Examples/Sources/MacroExamples/Implementation/CodableKey.swift rename to Examples/Sources/MacroExamples/Implementation/Peer/CodableKey.swift From d13946d52c5ca96ee67988790df3c512d1648f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ba=CC=A8k?= Date: Wed, 6 Sep 2023 13:57:40 +0200 Subject: [PATCH 2/5] Categorize Macro Interfaces This commit extends the categorization to macro interfaces --- .../Implementation/Member/CustomCodable.swift | 11 +++ .../{Macros.swift => ComplexMacros.swift} | 67 ++----------------- .../Interface/ExpressionMacros.swift | 62 +++++++++++++++++ .../MemberAttributeMacros.swift} | 18 ++--- .../{NewType.swift => MemberMacros.swift} | 28 ++++++-- .../MacroExamples/Interface/PeerMacros.swift | 24 +++++++ 6 files changed, 129 insertions(+), 81 deletions(-) rename Examples/Sources/MacroExamples/Interface/{Macros.swift => ComplexMacros.swift} (55%) create mode 100644 Examples/Sources/MacroExamples/Interface/ExpressionMacros.swift rename Examples/Sources/MacroExamples/{Implementation/Peer/CodableKey.swift => Interface/MemberAttributeMacros.swift} (56%) rename Examples/Sources/MacroExamples/Interface/{NewType.swift => MemberMacros.swift} (71%) create mode 100644 Examples/Sources/MacroExamples/Interface/PeerMacros.swift diff --git a/Examples/Sources/MacroExamples/Implementation/Member/CustomCodable.swift b/Examples/Sources/MacroExamples/Implementation/Member/CustomCodable.swift index 242ebbe2b18..fc5463f94cc 100644 --- a/Examples/Sources/MacroExamples/Implementation/Member/CustomCodable.swift +++ b/Examples/Sources/MacroExamples/Implementation/Member/CustomCodable.swift @@ -54,3 +54,14 @@ public enum CustomCodable: MemberMacro { 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 [] + } +} diff --git a/Examples/Sources/MacroExamples/Interface/Macros.swift b/Examples/Sources/MacroExamples/Interface/ComplexMacros.swift similarity index 55% rename from Examples/Sources/MacroExamples/Interface/Macros.swift rename to Examples/Sources/MacroExamples/Interface/ComplexMacros.swift index 609ceaf91d7..49bec94696e 100644 --- a/Examples/Sources/MacroExamples/Interface/Macros.swift +++ b/Examples/Sources/MacroExamples/Interface/ComplexMacros.swift @@ -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(_ 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(_ 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(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. @@ -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 { @@ -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` 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. /// diff --git a/Examples/Sources/MacroExamples/Interface/ExpressionMacros.swift b/Examples/Sources/MacroExamples/Interface/ExpressionMacros.swift new file mode 100644 index 00000000000..d38395cfcba --- /dev/null +++ b/Examples/Sources/MacroExamples/Interface/ExpressionMacros.swift @@ -0,0 +1,62 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation + +// MARK: - Add Blocker + +/// Macro that produces a warning on "+" operators within the expression, and +/// suggests changing them to "-". +@freestanding(expression) +public macro addBlocker(_ value: T) -> T = #externalMacro(module: "MacroExamplesImplementation", type: "AddBlocker") + +// MARK: - Font Literal + +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(name: String, size: Int, weight: FontWeight) -> T = #externalMacro( + module: "MacroExamplesImplementation", + type: "FontLiteralMacro" +) where T: ExpressibleByFontLiteral + +// MARK: - Stringify Macro + +/// "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(_ value: T) -> (T, String) = #externalMacro(module: "MacroExamplesImplementation", type: "StringifyMacro") + +// MARK: - URL + +/// 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") + +// MARK: - Warning + +/// 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") diff --git a/Examples/Sources/MacroExamples/Implementation/Peer/CodableKey.swift b/Examples/Sources/MacroExamples/Interface/MemberAttributeMacros.swift similarity index 56% rename from Examples/Sources/MacroExamples/Implementation/Peer/CodableKey.swift rename to Examples/Sources/MacroExamples/Interface/MemberAttributeMacros.swift index e29397f268d..ebaf056499d 100644 --- a/Examples/Sources/MacroExamples/Implementation/Peer/CodableKey.swift +++ b/Examples/Sources/MacroExamples/Interface/MemberAttributeMacros.swift @@ -10,16 +10,10 @@ // //===----------------------------------------------------------------------===// -import SwiftSyntax -import SwiftSyntaxMacros +// MARK: - Wrap Stored Properties -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 [] - } -} +/// 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") diff --git a/Examples/Sources/MacroExamples/Interface/NewType.swift b/Examples/Sources/MacroExamples/Interface/MemberMacros.swift similarity index 71% rename from Examples/Sources/MacroExamples/Interface/NewType.swift rename to Examples/Sources/MacroExamples/Interface/MemberMacros.swift index 16abf832edb..f08d6742dfb 100644 --- a/Examples/Sources/MacroExamples/Interface/NewType.swift +++ b/Examples/Sources/MacroExamples/Interface/MemberMacros.swift @@ -10,12 +10,28 @@ // //===----------------------------------------------------------------------===// -@attached( - member, - names: named(RawValue), - named(rawValue), - arbitrary // should be named(init(_:)) but that doesn't compile as of swift-DEVELOPMENT-SNAPSHOT-2023-02-02-a -) +// MARK: - Case Detection + +/// Add computed properties named `is` for each case element in the enum. +@attached(member, names: arbitrary) +public macro CaseDetection() = #externalMacro(module: "MacroExamplesImplementation", type: "CaseDetectionMacro") + +// MARK: - Custom Codable + +@attached(member, names: named(CodingKeys)) +public macro CustomCodable() = #externalMacro(module: "MacroExamplesImplementation", type: "CustomCodable") + +@attached(peer) +public macro CodableKey(name: String) = #externalMacro(module: "MacroExamplesImplementation", type: "CodableKey") + +// MARK: - Meta Enum + +@attached(member, names: named(Meta)) +public macro MetaEnum() = #externalMacro(module: "MacroExamplesImplementation", type: "MetaEnumMacro") + +// MARK: - New Type + +@attached(member, names: named(RawValue), named(rawValue), named(init(_:))) public macro NewType(_: T.Type) = #externalMacro(module: "MacroExamplesImplementation", type: "NewTypeMacro") public protocol NewTypeProtocol: RawRepresentable { diff --git a/Examples/Sources/MacroExamples/Interface/PeerMacros.swift b/Examples/Sources/MacroExamples/Interface/PeerMacros.swift new file mode 100644 index 00000000000..b16c8039410 --- /dev/null +++ b/Examples/Sources/MacroExamples/Interface/PeerMacros.swift @@ -0,0 +1,24 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +// MARK: - Add Async + +@attached(peer, names: overloaded) +public macro AddAsync() = #externalMacro(module: "MacroExamplesImplementation", type: "AddAsyncMacro") + +// MARK: - Add Completion Handler + +/// 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") From 8e89702af4c7c0bbeadea8483c2daa996e753475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ba=CC=A8k?= Date: Fri, 8 Sep 2023 17:55:32 +0200 Subject: [PATCH 3/5] Categorize Macro Playgorud by Macro Type --- .../Playground/ComplexMacrosPlayground.swift | 73 ++++++ .../ExpressionMacrosPlayground.swift | 59 +++++ .../MemberAttributeMacrosPlayground.swift | 28 +++ .../Playground/MemberMacrosPlayground.swift | 94 ++++++++ .../Playground/PeerMacrosPlayground.swift | 49 ++++ .../MacroExamples/Playground/main.swift | 226 +++--------------- 6 files changed, 335 insertions(+), 194 deletions(-) create mode 100644 Examples/Sources/MacroExamples/Playground/ComplexMacrosPlayground.swift create mode 100644 Examples/Sources/MacroExamples/Playground/ExpressionMacrosPlayground.swift create mode 100644 Examples/Sources/MacroExamples/Playground/MemberAttributeMacrosPlayground.swift create mode 100644 Examples/Sources/MacroExamples/Playground/MemberMacrosPlayground.swift create mode 100644 Examples/Sources/MacroExamples/Playground/PeerMacrosPlayground.swift diff --git a/Examples/Sources/MacroExamples/Playground/ComplexMacrosPlayground.swift b/Examples/Sources/MacroExamples/Playground/ComplexMacrosPlayground.swift new file mode 100644 index 00000000000..8e02d839d9a --- /dev/null +++ b/Examples/Sources/MacroExamples/Playground/ComplexMacrosPlayground.swift @@ -0,0 +1,73 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesInterface + +// MARK: - Dictionary Storage + +// Move the storage from each of the stored properties into a dictionary +// called `_storage`, turning the stored properties into computed properties. +@DictionaryStorage +struct Point { + var x: Int = 1 + var y: Int = 2 +} + +func runDictionaryStorageMacroPlayground() { + var point = Point() + print("Point storage begins as an empty dictionary: \(point)") + print("Default value for point.x: \(point.x)") + point.y = 17 + print("Point storage contains only the value we set: \(point)") +} + +// MARK: - Observable + +struct Treat {} + +@Observable +final class Dog { + var name: String? + var treat: Treat? + + var isHappy: Bool = true + + init() {} + + func bark() { + print("bork bork") + } +} + +func runObservableMacroPlayground() { + let dog = Dog() + print(dog.name ?? "") + dog.name = "George" + dog.treat = Treat() + print(dog.name ?? "") + dog.bark() +} + +// MARK: - Option Set + +@MyOptionSet +struct ShippingOptions { + private enum Options: Int { + case nextDay + case secondDay + case priority + case standard + } + + static let express: ShippingOptions = [.nextDay, .secondDay] + static let all: ShippingOptions = [.express, .priority, .standard] +} diff --git a/Examples/Sources/MacroExamples/Playground/ExpressionMacrosPlayground.swift b/Examples/Sources/MacroExamples/Playground/ExpressionMacrosPlayground.swift new file mode 100644 index 00000000000..1fea4704133 --- /dev/null +++ b/Examples/Sources/MacroExamples/Playground/ExpressionMacrosPlayground.swift @@ -0,0 +1,59 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation +import MacroExamplesInterface + +func runExpressionMacrosPlayground() { + + let x = 1 + let y = 2 + let z = 3 + + // MARK: - Add Blocker + + // "AddBlocker" complains about addition operations. We emit a warning + // so it doesn't block compilation. + print(#addBlocker(x * y + z)) + + // MARK: - Font Literal + + struct Font: ExpressibleByFontLiteral { + init(fontLiteralName: String, size: Int, weight: MacroExamplesInterface.FontWeight) { + } + } + + let _: Font = #fontLiteral(name: "Comic Sans", size: 14, weight: .thin) + + // MARK: - Stringify Macro + + // "Stringify" macro turns the expression into a string. + print(#stringify(x + y)) + + // MARK: - URL + + // "#URL" macro provides compile time checked URL construction. If the URL is + // malformed an error is emitted. Otherwise a non-optional URL is expanded. + print(#URL("https://swift.org/")) + + let domain = "domain.com" + //print(#URL("https://\(domain)/api/path")) // error: #URL requires a static string literal + //print(#URL("https://not a url.com")) // error: Malformed url + + // MARK: - Warning + + #myWarning("remember to pass a string literal here") + + // Uncomment to get an error out of the macro. + // let text = "oops" + // #myWarning(text) +} diff --git a/Examples/Sources/MacroExamples/Playground/MemberAttributeMacrosPlayground.swift b/Examples/Sources/MacroExamples/Playground/MemberAttributeMacrosPlayground.swift new file mode 100644 index 00000000000..11ff1d3458e --- /dev/null +++ b/Examples/Sources/MacroExamples/Playground/MemberAttributeMacrosPlayground.swift @@ -0,0 +1,28 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesInterface + +func runMemberAttributeMacrosPlayground() { + // MARK: - Wrap Stored Properties + + // Use the "wrapStoredProperties" macro to deprecate all of the stored + // properties.b + @wrapStoredProperties(#"available(*, deprecated, message: "hands off my data")"#) + struct OldStorage { + var x: Int + } + + // The deprecation warning below comes from the deprecation attribute + // introduced by @wrapStoredProperties on OldStorage. + _ = OldStorage(x: 5).x +} diff --git a/Examples/Sources/MacroExamples/Playground/MemberMacrosPlayground.swift b/Examples/Sources/MacroExamples/Playground/MemberMacrosPlayground.swift new file mode 100644 index 00000000000..5a67fa231be --- /dev/null +++ b/Examples/Sources/MacroExamples/Playground/MemberMacrosPlayground.swift @@ -0,0 +1,94 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation +import MacroExamplesInterface + +func runMemberMacrosPlayground() { + // MARK: - Case Detection + + @CaseDetection + enum Pet { + case dog + case cat(curious: Bool) + case parrot + case snake + } + + let pet: Pet = .cat(curious: true) + print("Pet is dog: \(pet.isDog)") + print("Pet is cat: \(pet.isCat)") + + // MARK: - Custom Codable + + @CustomCodable + struct CustomCodableString: Codable { + + @CodableKey(name: "OtherName") + var propertyWithOtherName: String + + var propertyWithSameName: Bool + + func randomFunction() { + + } + } + + let json = """ + { + "OtherName": "Name", + "propertyWithSameName": true + } + + """.data(using: .utf8)! + + let jsonDecoder = JSONDecoder() + let product = try! jsonDecoder.decode(CustomCodableString.self, from: json) + print(product.propertyWithOtherName) + + // MARK: - Meta Enum + + // `@MetaEnum` adds a nested enum called `Meta` with the same cases, but no + // associated values/payloads. Handy for e.g. describing a schema. + @MetaEnum enum Value { + case integer(Int) + case text(String) + case boolean(Bool) + case null + } + + print(Value.Meta(.integer(42)) == .integer) + + // MARK: - New Type + + @NewType(String.self) + struct Hostname: + NewTypeProtocol, + Hashable, + CustomStringConvertible + {} + + @NewType(String.self) + struct Password: + NewTypeProtocol, + Hashable, + CustomStringConvertible + { + var description: String { "(redacted)" } + } + + let hostname = Hostname("localhost") + print("hostname: description=\(hostname) hashValue=\(hostname.hashValue)") + + let password = Password("squeamish ossifrage") + print("password: description=\(password) hashValue=\(password.hashValue)") +} diff --git a/Examples/Sources/MacroExamples/Playground/PeerMacrosPlayground.swift b/Examples/Sources/MacroExamples/Playground/PeerMacrosPlayground.swift new file mode 100644 index 00000000000..9030e7859c0 --- /dev/null +++ b/Examples/Sources/MacroExamples/Playground/PeerMacrosPlayground.swift @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesInterface + +func runPeerMacrosPlayground() { + // MARK: - Add Async + + struct MyStruct { + @AddAsync + func c(a: Int, for b: String, _ value: Double, completionBlock: @escaping (Result) -> Void) -> Void { + completionBlock(.success("a: \(a), b: \(b), value: \(value)")) + } + + @AddAsync + func d(a: Int, for b: String, _ value: Double, completionBlock: @escaping (Bool) -> Void) -> Void { + completionBlock(true) + } + } + + Task { + let myStruct = MyStruct() + _ = try? await myStruct.c(a: 5, for: "Test", 20) + + _ = await myStruct.d(a: 10, for: "value", 40) + } + + // MARK: - Add Completion Handler + + class MyClass { + @AddCompletionHandler + func f(a: Int, for b: String, _ value: Double) async -> String { + return b + } + } + + MyClass().f(a: 1, for: "hello", 3.14159) { result in + print("Eventually received \(result + "!")") + } +} diff --git a/Examples/Sources/MacroExamples/Playground/main.swift b/Examples/Sources/MacroExamples/Playground/main.swift index 6df22c096d6..3a6de158f84 100644 --- a/Examples/Sources/MacroExamples/Playground/main.swift +++ b/Examples/Sources/MacroExamples/Playground/main.swift @@ -13,197 +13,35 @@ import Foundation import MacroExamplesInterface -let x = 1 -let y = 2 -let z = 3 - -// "Stringify" macro turns the expression into a string. -print(#stringify(x + y)) - -// "AddBlocker" complains about addition operations. We emit a warning -// so it doesn't block compilation. -print(#addBlocker(x * y + z)) - -#myWarning("remember to pass a string literal here") - -// Uncomment to get an error out of the macro. -// let text = "oops" -// #myWarning(text) - -struct Font: ExpressibleByFontLiteral { - init(fontLiteralName: String, size: Int, weight: MacroExamplesInterface.FontWeight) { - } -} - -let _: Font = #fontLiteral(name: "Comic Sans", size: 14, weight: .thin) - -// "#URL" macro provides compile time checked URL construction. If the URL is -// malformed an error is emitted. Otherwise a non-optional URL is expanded. -print(#URL("https://swift.org/")) - -let domain = "domain.com" -//print(#URL("https://\(domain)/api/path")) // error: #URL requires a static string literal -//print(#URL("https://not a url.com")) // error: Malformed url - -// Use the "wrapStoredProperties" macro to deprecate all of the stored -// properties. -@wrapStoredProperties(#"available(*, deprecated, message: "hands off my data")"#) -struct OldStorage { - var x: Int -} - -// The deprecation warning below comes from the deprecation attribute -// introduced by @wrapStoredProperties on OldStorage. -_ = OldStorage(x: 5).x - -// Move the storage from each of the stored properties into a dictionary -// called `_storage`, turning the stored properties into computed properties. -@DictionaryStorage -struct Point { - var x: Int = 1 - var y: Int = 2 -} - -@CaseDetection -enum Pet { - case dog - case cat(curious: Bool) - case parrot - case snake -} - -let pet: Pet = .cat(curious: true) -print("Pet is dog: \(pet.isDog)") -print("Pet is cat: \(pet.isCat)") - -var point = Point() -print("Point storage begins as an empty dictionary: \(point)") -print("Default value for point.x: \(point.x)") -point.y = 17 -print("Point storage contains only the value we set: \(point)") - -// MARK: - ObservableMacro - -struct Treat {} - -@Observable -final class Dog { - var name: String? - var treat: Treat? - - var isHappy: Bool = true - - init() {} - - func bark() { - print("bork bork") - } -} - -let dog = Dog() -print(dog.name ?? "") -dog.name = "George" -dog.treat = Treat() -print(dog.name ?? "") -dog.bark() - -// MARK: NewType - -@NewType(String.self) -struct Hostname: - NewTypeProtocol, - Hashable, - CustomStringConvertible -{} - -@NewType(String.self) -struct Password: - NewTypeProtocol, - Hashable, - CustomStringConvertible -{ - var description: String { "(redacted)" } -} - -let hostname = Hostname("localhost") -print("hostname: description=\(hostname) hashValue=\(hostname.hashValue)") - -let password = Password("squeamish ossifrage") -print("password: description=\(password) hashValue=\(password.hashValue)") - -struct MyStruct { - @AddCompletionHandler - func f(a: Int, for b: String, _ value: Double) async -> String { - return b - } - - @AddAsync - func c(a: Int, for b: String, _ value: Double, completionBlock: @escaping (Result) -> Void) -> Void { - completionBlock(.success("a: \(a), b: \(b), value: \(value)")) - } - - @AddAsync - func d(a: Int, for b: String, _ value: Double, completionBlock: @escaping (Bool) -> Void) -> Void { - completionBlock(true) - } -} - -@CustomCodable -struct CustomCodableString: Codable { - - @CodableKey(name: "OtherName") - var propertyWithOtherName: String - - var propertyWithSameName: Bool - - func randomFunction() { - - } -} - -Task { - let myStruct = MyStruct() - _ = try? await myStruct.c(a: 5, for: "Test", 20) - - _ = await myStruct.d(a: 10, for: "value", 40) -} - -MyStruct().f(a: 1, for: "hello", 3.14159) { result in - print("Eventually received \(result + "!")") -} - -let json = """ - { - "OtherName": "Name", - "propertyWithSameName": true - } - - """.data(using: .utf8)! - -let jsonDecoder = JSONDecoder() -let product = try jsonDecoder.decode(CustomCodableString.self, from: json) -print(product.propertyWithOtherName) - -@MyOptionSet -struct ShippingOptions { - private enum Options: Int { - case nextDay - case secondDay - case priority - case standard - } - - static let express: ShippingOptions = [.nextDay, .secondDay] - static let all: ShippingOptions = [.express, .priority, .standard] -} - -// `@MetaEnum` adds a nested enum called `Meta` with the same cases, but no -// associated values/payloads. Handy for e.g. describing a schema. -@MetaEnum enum Value { - case integer(Int) - case text(String) - case boolean(Bool) - case null -} - -print(Value.Meta(.integer(42)) == .integer) +// MARK: - Accessor Macros + +// TODO: Add example of an accessor macro + +// MARK: - Complex Macros + +runDictionaryStorageMacroPlayground() +runObservableMacroPlayground() + +// MARK: - Declaration Macros + +// TODO: Add example of declaration macro + +// MARK: - Expression Macros + +runExpressionMacrosPlayground() + +// MARK: - Extension Macros + +// TODO: Add example of extension macro + +// MARK: - Member Attribute Macros + +runMemberAttributeMacrosPlayground() + +// MARK: - Member Macros + +runMemberMacrosPlayground() + +// MARK: - Peer Macros + +runPeerMacrosPlayground() From 96acf684428a8973914b31ad554b2a3c6e17c680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ba=CC=A8k?= Date: Thu, 7 Sep 2023 17:37:42 +0200 Subject: [PATCH 4/5] Add unit tests to all macro examples --- Examples/Package.swift | 4 +- .../Expression/FontLiteralMacro.swift | 3 +- .../Interface/ExpressionMacros.swift | 9 +- .../DictionaryIndirectionMacroTests.swift | 111 +++++++++ .../ComplexMacros/ObservableMacroTests.swift | 141 +++++++++++ .../ComplexMacros/OptionSetMacroTests.swift | 225 ++++++++++++++++++ .../Expression/AddBlockerTests.swift | 54 +++++ .../Expression/FontLiteralMacroTests.swift | 46 ++++ .../Expression/StringifyMacroTests.swift | 46 ++++ .../Expression/URLMacroTests.swift | 65 +++++ .../Expression/WarningMacroTests.swift | 84 +++++++ .../MacroExamplesPluginTest.swift | 66 ----- .../Member/CaseDetectionMacroTests.swift | 56 +++++ .../Member/CustomCodableTests.swift | 74 ++++++ .../{ => Member}/MetaEnumMacroTests.swift | 111 ++++----- .../Member/NewTypeMacroTests.swift | 116 +++++++++ .../WrapStoredPropertiesMacroTests.swift | 108 +++++++++ .../Implementation/NewTypePluginTests.swift | 60 ----- .../Peer/AddAsyncMacroTests.swift | 135 +++++++++++ .../Peer/AddCompletionHandlerMacroTests.swift | 101 ++++++++ 20 files changed, 1416 insertions(+), 199 deletions(-) create mode 100644 Examples/Tests/MacroExamples/Implementation/ComplexMacros/DictionaryIndirectionMacroTests.swift create mode 100644 Examples/Tests/MacroExamples/Implementation/ComplexMacros/ObservableMacroTests.swift create mode 100644 Examples/Tests/MacroExamples/Implementation/ComplexMacros/OptionSetMacroTests.swift create mode 100644 Examples/Tests/MacroExamples/Implementation/Expression/AddBlockerTests.swift create mode 100644 Examples/Tests/MacroExamples/Implementation/Expression/FontLiteralMacroTests.swift create mode 100644 Examples/Tests/MacroExamples/Implementation/Expression/StringifyMacroTests.swift create mode 100644 Examples/Tests/MacroExamples/Implementation/Expression/URLMacroTests.swift create mode 100644 Examples/Tests/MacroExamples/Implementation/Expression/WarningMacroTests.swift delete mode 100644 Examples/Tests/MacroExamples/Implementation/MacroExamplesPluginTest.swift create mode 100644 Examples/Tests/MacroExamples/Implementation/Member/CaseDetectionMacroTests.swift create mode 100644 Examples/Tests/MacroExamples/Implementation/Member/CustomCodableTests.swift rename Examples/Tests/MacroExamples/Implementation/{ => Member}/MetaEnumMacroTests.swift (54%) create mode 100644 Examples/Tests/MacroExamples/Implementation/Member/NewTypeMacroTests.swift create mode 100644 Examples/Tests/MacroExamples/Implementation/MemberAttribute/WrapStoredPropertiesMacroTests.swift delete mode 100644 Examples/Tests/MacroExamples/Implementation/NewTypePluginTests.swift create mode 100644 Examples/Tests/MacroExamples/Implementation/Peer/AddAsyncMacroTests.swift create mode 100644 Examples/Tests/MacroExamples/Implementation/Peer/AddCompletionHandlerMacroTests.swift diff --git a/Examples/Package.swift b/Examples/Package.swift index c8b57bdead1..5e25cbb86fc 100644 --- a/Examples/Package.swift +++ b/Examples/Package.swift @@ -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" ), diff --git a/Examples/Sources/MacroExamples/Implementation/Expression/FontLiteralMacro.swift b/Examples/Sources/MacroExamples/Implementation/Expression/FontLiteralMacro.swift index a9e3d3f2cc3..0559ace73d3 100644 --- a/Examples/Sources/MacroExamples/Implementation/Expression/FontLiteralMacro.swift +++ b/Examples/Sources/MacroExamples/Implementation/Expression/FontLiteralMacro.swift @@ -40,7 +40,8 @@ private func replaceFirstLabel( } var tuple = tuple - tuple[tuple.startIndex] = firstElement + tuple[tuple.startIndex] = + firstElement .with(\.label, .identifier(newLabel)) .with(\.colon, .colonToken()) return tuple diff --git a/Examples/Sources/MacroExamples/Interface/ExpressionMacros.swift b/Examples/Sources/MacroExamples/Interface/ExpressionMacros.swift index d38395cfcba..647171d76ee 100644 --- a/Examples/Sources/MacroExamples/Interface/ExpressionMacros.swift +++ b/Examples/Sources/MacroExamples/Interface/ExpressionMacros.swift @@ -35,10 +35,11 @@ public protocol ExpressibleByFontLiteral { /// Font literal similar to, e.g., #colorLiteral. @freestanding(expression) -public macro fontLiteral(name: String, size: Int, weight: FontWeight) -> T = #externalMacro( - module: "MacroExamplesImplementation", - type: "FontLiteralMacro" -) where T: ExpressibleByFontLiteral +public macro fontLiteral(name: String, size: Int, weight: FontWeight) -> T = + #externalMacro( + module: "MacroExamplesImplementation", + type: "FontLiteralMacro" + ) where T: ExpressibleByFontLiteral // MARK: - Stringify Macro diff --git a/Examples/Tests/MacroExamples/Implementation/ComplexMacros/DictionaryIndirectionMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/ComplexMacros/DictionaryIndirectionMacroTests.swift new file mode 100644 index 00000000000..0b2b69dee94 --- /dev/null +++ b/Examples/Tests/MacroExamples/Implementation/ComplexMacros/DictionaryIndirectionMacroTests.swift @@ -0,0 +1,111 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesImplementation +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class DictionaryStorageMacroTests: XCTestCase { + private let macros: [String: Macro.Type] = [ + "DictionaryStorage": DictionaryStorageMacro.self, + "DictionaryStorageProperty": DictionaryStoragePropertyMacro.self, + ] + + func testExpansionConvertsStoredProperties() { + assertMacroExpansion( + """ + @DictionaryStorage + struct Point { + var x: Int = 1 + var y: Int = 2 + } + """, + expandedSource: """ + struct Point { + var x: Int = 1 { + get { + _storage["x", default: 1] as! Int + } + set { + _storage["x"] = newValue + } + } + var y: Int = 2 { + get { + _storage["y", default: 2] as! Int + } + set { + _storage["y"] = newValue + } + } + + var _storage: [String: Any] = [:] + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionWithoutInitializersEmitsError() { + assertMacroExpansion( + """ + @DictionaryStorage + class Point { + let x: Int + let y: Int + } + """, + expandedSource: """ + class Point { + let x: Int + let y: Int + + var _storage: [String: Any] = [:] + } + """, + diagnostics: [ + DiagnosticSpec(message: "stored property must have an initializer", line: 3, column: 3), + DiagnosticSpec(message: "stored property must have an initializer", line: 4, column: 3), + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionIgnoresComputedProperties() { + assertMacroExpansion( + """ + @DictionaryStorage + struct Test { + var value: Int { + get { return 0 } + set {} + } + } + """, + expandedSource: """ + struct Test { + var value: Int { + get { return 0 } + set {} + } + + var _storage: [String: Any] = [:] + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } +} diff --git a/Examples/Tests/MacroExamples/Implementation/ComplexMacros/ObservableMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/ComplexMacros/ObservableMacroTests.swift new file mode 100644 index 00000000000..13706868314 --- /dev/null +++ b/Examples/Tests/MacroExamples/Implementation/ComplexMacros/ObservableMacroTests.swift @@ -0,0 +1,141 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesImplementation +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class ObservableMacroTests: XCTestCase { + private let macros: [String: Macro.Type] = [ + "Observable": ObservableMacro.self, + "ObservableProperty": ObservablePropertyMacro.self, + ] + + func testExpansion() { + assertMacroExpansion( + """ + @Observable + final class Dog { + var name: String? + var treat: Treat? + + var isHappy: Bool = true + + init() {} + + func bark() { + print("bork bork") + } + } + """, + expandedSource: #""" + final class Dog { + var name: String? { + get { + _registrar.beginAccess(\.name) + defer { + _registrar.endAccess() + } + return _storage.name + } + set { + _registrar.beginAccess(\.name) + _registrar.register(observable: self, willSet: \.name, to: newValue) + defer { + _registrar.register(observable: self, didSet: \.name) + _registrar.endAccess() + } + _storage.name = newValue + } + } + var treat: Treat? { + get { + _registrar.beginAccess(\.treat) + defer { + _registrar.endAccess() + } + return _storage.treat + } + set { + _registrar.beginAccess(\.treat) + _registrar.register(observable: self, willSet: \.treat, to: newValue) + defer { + _registrar.register(observable: self, didSet: \.treat) + _registrar.endAccess() + } + _storage.treat = newValue + } + } + + var isHappy: Bool = true { + get { + _registrar.beginAccess(\.isHappy) + defer { + _registrar.endAccess() + } + return _storage.isHappy + } + set { + _registrar.beginAccess(\.isHappy) + _registrar.register(observable: self, willSet: \.isHappy, to: newValue) + defer { + _registrar.register(observable: self, didSet: \.isHappy) + _registrar.endAccess() + } + _storage.isHappy = newValue + } + } + + init() {} + + func bark() { + print("bork bork") + } + + let _registrar = ObservationRegistrar() + + public nonisolated func addObserver(_ observer: some Observer) { + _registrar.addObserver(observer) + } + + public nonisolated func removeObserver(_ observer: some Observer) { + _registrar.removeObserver(observer) + } + + private func withTransaction(_ apply: () throws -> T) rethrows -> T { + _registrar.beginAccess() + defer { + _registrar.endAccess() + } + return try apply() + } + + private struct Storage { + + var name: String? + var treat: Treat? + + var isHappy: Bool = true + } + + private var _storage = Storage() + } + + extension Dog: Observable { + } + """#, + macros: macros, + indentationWidth: .spaces(2) + ) + } +} diff --git a/Examples/Tests/MacroExamples/Implementation/ComplexMacros/OptionSetMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/ComplexMacros/OptionSetMacroTests.swift new file mode 100644 index 00000000000..0f09a656253 --- /dev/null +++ b/Examples/Tests/MacroExamples/Implementation/ComplexMacros/OptionSetMacroTests.swift @@ -0,0 +1,225 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesImplementation +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class OptionSetMacroTests: XCTestCase { + private let macros = ["MyOptionSet": OptionSetMacro.self] + + func testExpansionOnStructWithNestedEnumAndStatics() { + assertMacroExpansion( + """ + @MyOptionSet + struct ShippingOptions { + private enum Options: Int { + case nextDay + case secondDay + case priority + case standard + } + + static let express: ShippingOptions = [.nextDay, .secondDay] + static let all: ShippingOptions = [.express, .priority, .standard] + } + """, + expandedSource: """ + struct ShippingOptions { + private enum Options: Int { + case nextDay + case secondDay + case priority + case standard + } + + static let express: ShippingOptions = [.nextDay, .secondDay] + static let all: ShippingOptions = [.express, .priority, .standard] + + typealias RawValue = UInt8 + + var rawValue: RawValue + + init() { + self.rawValue = 0 + } + + init(rawValue: RawValue) { + self.rawValue = rawValue + } + + static let nextDay: Self = + Self (rawValue: 1 << Options.nextDay.rawValue) + + static let secondDay: Self = + Self (rawValue: 1 << Options.secondDay.rawValue) + + static let priority: Self = + Self (rawValue: 1 << Options.priority.rawValue) + + static let standard: Self = + Self (rawValue: 1 << Options.standard.rawValue) + } + + extension ShippingOptions: OptionSet { + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionOnPublicStructWithExplicitOptionSetConformance() { + assertMacroExpansion( + """ + @MyOptionSet + public struct ShippingOptions: OptionSet { + private enum Options: Int { + case nextDay + case standard + } + } + """, + expandedSource: """ + public struct ShippingOptions: OptionSet { + private enum Options: Int { + case nextDay + case standard + } + + public typealias RawValue = UInt8 + + public var rawValue: RawValue + + public init() { + self.rawValue = 0 + } + + public init(rawValue: RawValue) { + self.rawValue = rawValue + } + + public static let nextDay: Self = + Self (rawValue: 1 << Options.nextDay.rawValue) + + public static let standard: Self = + Self (rawValue: 1 << Options.standard.rawValue) + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionFailsOnEnumType() { + assertMacroExpansion( + """ + @MyOptionSet + enum Animal { + case dog + } + """, + expandedSource: """ + enum Animal { + case dog + } + """, + diagnostics: [ + DiagnosticSpec( + message: "'OptionSet' macro can only be applied to a struct", + line: 1, + column: 1 + ), + /// The diagnostic is duplicated because it's generated first by `ExtensionMacro` and then by `MemberMacro`. + /// Consider refactoring this in the future. + DiagnosticSpec( + message: "'OptionSet' macro can only be applied to a struct", + line: 1, + column: 1 + ), + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionFailsWithoutNestedOptionsEnum() { + assertMacroExpansion( + """ + @MyOptionSet + struct ShippingOptions { + static let express: ShippingOptions = [.nextDay, .secondDay] + static let all: ShippingOptions = [.express, .priority, .standard] + } + """, + expandedSource: """ + struct ShippingOptions { + static let express: ShippingOptions = [.nextDay, .secondDay] + static let all: ShippingOptions = [.express, .priority, .standard] + } + """, + diagnostics: [ + DiagnosticSpec( + message: "'OptionSet' macro requires nested options enum 'Options'", + line: 1, + column: 1 + ), + /// The diagnostic is duplicated because it's generated first by `ExtensionMacro` and then by `MemberMacro`. + /// Consider refactoring this in the future. + DiagnosticSpec( + message: "'OptionSet' macro requires nested options enum 'Options'", + line: 1, + column: 1 + ), + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionFailsWithoutSpecifiedRawType() { + assertMacroExpansion( + """ + @MyOptionSet + struct ShippingOptions { + private enum Options: Int { + case nextDay + } + } + """, + expandedSource: """ + struct ShippingOptions { + private enum Options: Int { + case nextDay + } + } + """, + diagnostics: [ + DiagnosticSpec( + message: "'OptionSet' macro requires a raw type", + line: 1, + column: 1 + ), + /// The diagnostic is duplicated because it's generated first by `ExtensionMacro` and then by `MemberMacro`. + /// Consider refactoring this in the future. + DiagnosticSpec( + message: "'OptionSet' macro requires a raw type", + line: 1, + column: 1 + ), + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } +} diff --git a/Examples/Tests/MacroExamples/Implementation/Expression/AddBlockerTests.swift b/Examples/Tests/MacroExamples/Implementation/Expression/AddBlockerTests.swift new file mode 100644 index 00000000000..d9895dfb725 --- /dev/null +++ b/Examples/Tests/MacroExamples/Implementation/Expression/AddBlockerTests.swift @@ -0,0 +1,54 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesImplementation +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class AddBlockerTests: XCTestCase { + private let macros = ["addBlocker": AddBlocker.self] + + func testExpansionTransformsAdditionToSubtractionAndEmitsWarning() { + assertMacroExpansion( + """ + #addBlocker(x * y + z) + """, + expandedSource: """ + x * y - z + """, + diagnostics: [ + DiagnosticSpec( + message: "blocked an add; did you mean to subtract?", + line: 1, + column: 19, + severity: .warning, + fixIts: [FixItSpec(message: "use '-'")] + ) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionPreservesSubtraction() { + assertMacroExpansion( + """ + #addBlocker(x * y - z) + """, + expandedSource: """ + x * y - z + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } +} diff --git a/Examples/Tests/MacroExamples/Implementation/Expression/FontLiteralMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/Expression/FontLiteralMacroTests.swift new file mode 100644 index 00000000000..1ee1db421c3 --- /dev/null +++ b/Examples/Tests/MacroExamples/Implementation/Expression/FontLiteralMacroTests.swift @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesImplementation +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class FontLiteralMacroTests: XCTestCase { + private let macros = ["fontLiteral": FontLiteralMacro.self] + + func testExpansionWithNamedArguments() { + assertMacroExpansion( + """ + #fontLiteral(name: "Comic Sans", size: 14, weight: .thin) + """, + expandedSource: """ + .init(fontLiteralName: "Comic Sans", size: 14, weight: .thin) + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionWithUnlabeledFirstArgument() { + assertMacroExpansion( + """ + #fontLiteral("Copperplate Gothic", size: 69, weight: .bold) + """, + expandedSource: """ + .init(fontLiteralName: "Copperplate Gothic", size: 69, weight: .bold) + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } +} diff --git a/Examples/Tests/MacroExamples/Implementation/Expression/StringifyMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/Expression/StringifyMacroTests.swift new file mode 100644 index 00000000000..99266cb0e74 --- /dev/null +++ b/Examples/Tests/MacroExamples/Implementation/Expression/StringifyMacroTests.swift @@ -0,0 +1,46 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesImplementation +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class StringifyMacroTests: XCTestCase { + private let macros = ["stringify": StringifyMacro.self] + + func testExpansionWithBasicArithmeticExpression() { + assertMacroExpansion( + """ + let a = #stringify(x + y) + """, + expandedSource: """ + let a = (x + y, "x + y") + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionWithStringInterpolation() { + assertMacroExpansion( + """ + let b = #stringify("Hello, \(name)") + """, + expandedSource: """ + let b = ("Hello, \(name)", #""Hello, \(name)""#) + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } +} diff --git a/Examples/Tests/MacroExamples/Implementation/Expression/URLMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/Expression/URLMacroTests.swift new file mode 100644 index 00000000000..b49a4133a1f --- /dev/null +++ b/Examples/Tests/MacroExamples/Implementation/Expression/URLMacroTests.swift @@ -0,0 +1,65 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesImplementation +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class URLMacroTests: XCTestCase { + private let macros = ["URL": URLMacro.self] + + func testExpansionWithMalformedURLEmitsError() { + assertMacroExpansion( + """ + let invalid = #URL("https://not a url.com") + """, + expandedSource: """ + let invalid = #URL("https://not a url.com") + """, + diagnostics: [ + DiagnosticSpec(message: #"malformed url: "https://not a url.com""#, line: 1, column: 15, severity: .error) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionWithStringInterpolationEmitsError() { + assertMacroExpansion( + #""" + #URL("https://\(domain)/api/path") + """#, + expandedSource: #""" + #URL("https://\(domain)/api/path") + """#, + diagnostics: [ + DiagnosticSpec(message: "#URL requires a static string literal", line: 1, column: 1, severity: .error) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionWithValidURL() { + assertMacroExpansion( + """ + let valid = #URL("https://swift.org/") + """, + expandedSource: """ + let valid = URL(string: "https://swift.org/")! + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } +} diff --git a/Examples/Tests/MacroExamples/Implementation/Expression/WarningMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/Expression/WarningMacroTests.swift new file mode 100644 index 00000000000..d5354cababc --- /dev/null +++ b/Examples/Tests/MacroExamples/Implementation/Expression/WarningMacroTests.swift @@ -0,0 +1,84 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesImplementation +import SwiftDiagnostics +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class WarningMacroTests: XCTestCase { + private let macros = ["myWarning": WarningMacro.self] + + func testExpansionWithValidStringLiteralEmitsWarning() { + assertMacroExpansion( + """ + #myWarning("This is a warning") + """, + expandedSource: """ + () + """, + diagnostics: [ + DiagnosticSpec( + message: "This is a warning", + line: 1, + column: 1, + severity: .warning + ) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionWithInvalidExpressionEmitsError() { + assertMacroExpansion( + """ + #myWarning(42) + """, + expandedSource: """ + #myWarning(42) + """, + diagnostics: [ + DiagnosticSpec( + message: "#myWarning macro requires a string literal", + line: 1, + column: 1, + severity: .error + ) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionWithStringInterpolationEmitsError() { + assertMacroExpansion( + #""" + #myWarning("Say hello \(number) times!") + """#, + expandedSource: #""" + #myWarning("Say hello \(number) times!") + """#, + diagnostics: [ + DiagnosticSpec( + message: "#myWarning macro requires a string literal", + line: 1, + column: 1, + severity: .error + ) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } +} diff --git a/Examples/Tests/MacroExamples/Implementation/MacroExamplesPluginTest.swift b/Examples/Tests/MacroExamples/Implementation/MacroExamplesPluginTest.swift deleted file mode 100644 index 28778075cb5..00000000000 --- a/Examples/Tests/MacroExamples/Implementation/MacroExamplesPluginTest.swift +++ /dev/null @@ -1,66 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import MacroExamplesImplementation -import SwiftSyntax -import SwiftSyntaxBuilder -import SwiftSyntaxMacroExpansion -import SwiftSyntaxMacros -import XCTest - -var testMacros: [String: Macro.Type] = [ - "stringify": StringifyMacro.self -] - -final class MacroExamplesPluginTests: XCTestCase { - func testStringify() { - let sf: SourceFileSyntax = - #""" - let a = #stringify(x + y) - let b = #stringify("Hello, \(name)") - """# - let context = BasicMacroExpansionContext.init( - sourceFiles: [sf: .init(moduleName: "MyModule", fullFilePath: "test.swift")] - ) - let transformedSF = sf.expand(macros: testMacros, in: context) - XCTAssertEqual( - transformedSF.description, - #""" - let a = (x + y, "x + y") - let b = ("Hello, \(name)", #""Hello, \(name)""#) - """# - ) - } - - func testURL() throws { - let sf: SourceFileSyntax = - #""" - let invalid = #URL("https://not a url.com") - let valid = #URL("https://swift.org/") - """# - let context = BasicMacroExpansionContext.init( - sourceFiles: [sf: .init(moduleName: "MyModule", fullFilePath: "test.swift")] - ) - let transformedSF = sf.expand(macros: ["URL": URLMacro.self], in: context) - XCTAssertEqual( - transformedSF.description, - #""" - let invalid = #URL("https://not a url.com") - let valid = URL(string: "https://swift.org/")! - """# - ) - XCTAssertEqual(context.diagnostics.count, 1) - let diagnostic = try XCTUnwrap(context.diagnostics.first) - XCTAssertEqual(diagnostic.message, #"malformed url: "https://not a url.com""#) - XCTAssertEqual(diagnostic.diagMessage.severity, .error) - } -} diff --git a/Examples/Tests/MacroExamples/Implementation/Member/CaseDetectionMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/Member/CaseDetectionMacroTests.swift new file mode 100644 index 00000000000..cd506d7bfe9 --- /dev/null +++ b/Examples/Tests/MacroExamples/Implementation/Member/CaseDetectionMacroTests.swift @@ -0,0 +1,56 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesImplementation +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class CaseDetectionMacroTests: XCTestCase { + private let macros = ["CaseDetection": CaseDetectionMacro.self] + + func testExpansionAddsComputedProperties() { + assertMacroExpansion( + """ + @CaseDetection + enum Animal { + case dog + case cat(curious: Bool) + } + """, + expandedSource: """ + enum Animal { + case dog + case cat(curious: Bool) + + var isDog: Bool { + if case .dog = self { + return true + } + + return false + } + + var isCat: Bool { + if case .cat = self { + return true + } + + return false + } + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } +} diff --git a/Examples/Tests/MacroExamples/Implementation/Member/CustomCodableTests.swift b/Examples/Tests/MacroExamples/Implementation/Member/CustomCodableTests.swift new file mode 100644 index 00000000000..1ad4d8b9b49 --- /dev/null +++ b/Examples/Tests/MacroExamples/Implementation/Member/CustomCodableTests.swift @@ -0,0 +1,74 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesImplementation +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class CustomCodableTests: XCTestCase { + private let macros = ["CustomCodable": CustomCodable.self] + + func testExpansionAddsDefaultCodingKeys() { + assertMacroExpansion( + """ + @CustomCodable + struct Person { + let name: String + let age: Int + } + """, + expandedSource: """ + struct Person { + let name: String + let age: Int + + enum CodingKeys: String, CodingKey { + case name + case age + } + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionWithCodableKeyAddsCustomCodingKeys() { + assertMacroExpansion( + """ + @CustomCodable + struct Person { + let name: String + @CodableKey("user_age") let age: Int + + func randomFunction() {} + } + """, + expandedSource: """ + struct Person { + let name: String + @CodableKey("user_age") let age: Int + + func randomFunction() {} + + enum CodingKeys: String, CodingKey { + case name + case age = "user_age" + } + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } +} diff --git a/Examples/Tests/MacroExamples/Implementation/MetaEnumMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/Member/MetaEnumMacroTests.swift similarity index 54% rename from Examples/Tests/MacroExamples/Implementation/MetaEnumMacroTests.swift rename to Examples/Tests/MacroExamples/Implementation/Member/MetaEnumMacroTests.swift index 9e852839dfb..305bdbb6428 100644 --- a/Examples/Tests/MacroExamples/Implementation/MetaEnumMacroTests.swift +++ b/Examples/Tests/MacroExamples/Implementation/Member/MetaEnumMacroTests.swift @@ -11,40 +11,29 @@ //===----------------------------------------------------------------------===// import MacroExamplesImplementation -import SwiftSyntax -import SwiftSyntaxBuilder -import SwiftSyntaxMacroExpansion import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport import XCTest -final class CaseMacroTests: XCTestCase { - let testMacros: [String: Macro.Type] = [ - "MetaEnum": MetaEnumMacro.self - ] +final class MetaEnumMacroTests: XCTestCase { + private let macros = ["MetaEnum": MetaEnumMacro.self] - func testBasic() throws { - let sf: SourceFileSyntax = """ + func testExpansionAddsNestedMetaEnum() { + assertMacroExpansion( + """ @MetaEnum enum Cell { case integer(Int) case text(String) case boolean(Bool) case null } - """ - - let context = BasicMacroExpansionContext( - sourceFiles: [sf: .init(moduleName: "MyModule", fullFilePath: "test.swift")] - ) - - let transformed = sf.expand(macros: testMacros, in: context) - XCTAssertEqual( - transformed.description, - """ - enum Cell { - case integer(Int) - case text(String) - case boolean(Bool) - case null + """, + expandedSource: """ + enum Cell { + case integer(Int) + case text(String) + case boolean(Bool) + case null enum Meta { case integer @@ -65,32 +54,27 @@ final class CaseMacroTests: XCTestCase { } } } - } - """ + } + """, + macros: macros, + indentationWidth: .spaces(2) ) } - func testPublic() throws { - let sf: SourceFileSyntax = """ + func testExpansionAddsPublicNestedMetaEnum() { + assertMacroExpansion( + """ @MetaEnum public enum Cell { case integer(Int) case text(String) case boolean(Bool) } - """ - - let context = BasicMacroExpansionContext( - sourceFiles: [sf: .init(moduleName: "MyModule", fullFilePath: "test.swift")] - ) - - let transformed = sf.expand(macros: testMacros, in: context) - XCTAssertEqual( - transformed.description, - """ - public enum Cell { - case integer(Int) - case text(String) - case boolean(Bool) + """, + expandedSource: """ + public enum Cell { + case integer(Int) + case text(String) + case boolean(Bool) public enum Meta { case integer @@ -108,39 +92,32 @@ final class CaseMacroTests: XCTestCase { } } } - } - """ + } + """, + macros: macros, + indentationWidth: .spaces(2) ) } - func testNonEnum() throws { - let sf: SourceFileSyntax = """ - @MetaEnum struct Cell { - let integer: Int - let text: String - let boolean: Bool - } - """ - - let context = BasicMacroExpansionContext( - sourceFiles: [sf: .init(moduleName: "MyModule", fullFilePath: "test.swift")] - ) - - let transformed = sf.expand(macros: testMacros, in: context) - XCTAssertEqual( - transformed.description, + func testExpansionOnStructEmitsError() { + assertMacroExpansion( """ - struct Cell { + @MetaEnum struct Cell { let integer: Int let text: String let boolean: Bool } - """ + """, + expandedSource: """ + struct Cell { + let integer: Int + let text: String + let boolean: Bool + } + """, + diagnostics: [DiagnosticSpec(message: "'@MetaEnum' can only be attached to an enum, not a struct", line: 1, column: 1, severity: .error)], + macros: macros, + indentationWidth: .spaces(2) ) - - XCTAssertEqual(context.diagnostics.count, 1) - let diag = try XCTUnwrap(context.diagnostics.first) - XCTAssertEqual(diag.message, "'@MetaEnum' can only be attached to an enum, not a struct") - XCTAssertEqual(diag.diagMessage.severity, .error) } } diff --git a/Examples/Tests/MacroExamples/Implementation/Member/NewTypeMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/Member/NewTypeMacroTests.swift new file mode 100644 index 00000000000..729e9c9e8e7 --- /dev/null +++ b/Examples/Tests/MacroExamples/Implementation/Member/NewTypeMacroTests.swift @@ -0,0 +1,116 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesImplementation +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class NewTypeMacroTests: XCTestCase { + private let macros = ["NewType": NewTypeMacro.self] + + func testExpansionAddsStringRawType() { + assertMacroExpansion( + """ + @NewType(String.self) + struct Username { + } + """, + expandedSource: """ + struct Username { + + typealias RawValue = String + + var rawValue: RawValue + + init(_ rawValue: RawValue) { + self.rawValue = rawValue + } + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionWithPublicAddsPublicStringRawType() { + assertMacroExpansion( + """ + @NewType(String.self) + public struct MyString { + } + """, + expandedSource: """ + public struct MyString { + + public typealias RawValue = String + + public var rawValue: RawValue + + public init(_ rawValue: RawValue) { + self.rawValue = rawValue + } + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionOnClassEmitsError() { + assertMacroExpansion( + """ + @NewType(Int.self) + class NotAUsername { + } + """, + expandedSource: """ + class NotAUsername { + } + """, + diagnostics: [ + DiagnosticSpec( + message: "@NewType can only be applied to a struct declarations.", + line: 1, + column: 1, + severity: .error + ) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionWithMissingRawTypeEmitsError() { + assertMacroExpansion( + """ + @NewType + struct NoRawType { + } + """, + expandedSource: """ + struct NoRawType { + } + """, + diagnostics: [ + DiagnosticSpec( + message: #"@NewType requires the raw type as an argument, in the form "RawType.self"."#, + line: 1, + column: 1, + severity: .error + ) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } +} diff --git a/Examples/Tests/MacroExamples/Implementation/MemberAttribute/WrapStoredPropertiesMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/MemberAttribute/WrapStoredPropertiesMacroTests.swift new file mode 100644 index 00000000000..4675eb9710a --- /dev/null +++ b/Examples/Tests/MacroExamples/Implementation/MemberAttribute/WrapStoredPropertiesMacroTests.swift @@ -0,0 +1,108 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesImplementation +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class WrapStoredPropertiesMacroTests: XCTestCase { + private let macros = ["wrapStoredProperties": WrapStoredPropertiesMacro.self] + + func testExpansionAddsPublished() { + assertMacroExpansion( + """ + @wrapStoredProperties("Published") + struct Test { + var value: Int + } + """, + expandedSource: """ + struct Test { + @Published + var value: Int + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionAddsDeprecationAttribute() { + assertMacroExpansion( + """ + @wrapStoredProperties(#"available(*, deprecated, message: "hands off my data")"#) + struct Test { + var value: Int + } + """, + expandedSource: """ + struct Test { + @available(*, deprecated, message: "hands off my data") + var value: Int + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionIgnoresComputedProperty() { + assertMacroExpansion( + """ + @wrapStoredProperties("Published") + struct Test { + var value: Int { + get { return 0 } + set {} + } + } + """, + expandedSource: """ + struct Test { + var value: Int { + get { return 0 } + set {} + } + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionWithInvalidAttributeEmitsError() { + assertMacroExpansion( + """ + @wrapStoredProperties(12) + struct Test { + var value: Int + } + """, + expandedSource: """ + struct Test { + var value: Int + } + """, + diagnostics: [ + DiagnosticSpec( + message: "macro requires a string literal containing the name of an attribute", + line: 1, + column: 1, + severity: .error + ) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } +} diff --git a/Examples/Tests/MacroExamples/Implementation/NewTypePluginTests.swift b/Examples/Tests/MacroExamples/Implementation/NewTypePluginTests.swift deleted file mode 100644 index 218c942cc90..00000000000 --- a/Examples/Tests/MacroExamples/Implementation/NewTypePluginTests.swift +++ /dev/null @@ -1,60 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import MacroExamplesImplementation -import SwiftSyntax -import SwiftSyntaxBuilder -import SwiftSyntaxMacroExpansion -import SwiftSyntaxMacros -import XCTest - -final class NewTypePluginTests: XCTestCase { - let testMacros: [String: Macro.Type] = [ - "NewType": NewTypeMacro.self - ] - - func testNewType() { - let sf: SourceFileSyntax = - #""" - @NewType(String.self) - public struct MyString { - } - """# - - // print(sf.recursiveDescription) - - let context = BasicMacroExpansionContext( - sourceFiles: [sf: .init(moduleName: "MyModule", fullFilePath: "test.swift")] - ) - - let transformed = sf.expand(macros: testMacros, in: context) - - // print(transformed.recursiveDescription) - - XCTAssertEqual( - transformed.description, - #""" - - public struct MyString { - - public typealias RawValue = String - - public var rawValue: RawValue - - public init(_ rawValue: RawValue) { - self.rawValue = rawValue - } - } - """# - ) - } -} diff --git a/Examples/Tests/MacroExamples/Implementation/Peer/AddAsyncMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/Peer/AddAsyncMacroTests.swift new file mode 100644 index 00000000000..16893bdb095 --- /dev/null +++ b/Examples/Tests/MacroExamples/Implementation/Peer/AddAsyncMacroTests.swift @@ -0,0 +1,135 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesImplementation +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class AddAsyncMacroTests: XCTestCase { + private let macros = ["AddAsync": AddAsyncMacro.self] + + func testExpansionTransformsFunctionWithResultCompletionToAsyncThrows() { + assertMacroExpansion( + #""" + @AddAsync + func c(a: Int, for b: String, _ value: Double, completionBlock: @escaping (Result) -> Void) -> Void { + completionBlock(.success("a: \(a), b: \(b), value: \(value)")) + } + """#, + expandedSource: #""" + func c(a: Int, for b: String, _ value: Double, completionBlock: @escaping (Result) -> Void) -> Void { + completionBlock(.success("a: \(a), b: \(b), value: \(value)")) + } + + func c(a: Int, for b: String, _ value: Double) async throws -> String { + try await withCheckedThrowingContinuation { continuation in + c(a: a, for: b, value) { returnValue in + + switch returnValue { + case .success(let value): + continuation.resume(returning: value) + case .failure(let error): + continuation.resume(throwing: error) + } + + } + } + } + """#, + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionTransformsFunctionWithBoolCompletionToAsync() { + assertMacroExpansion( + """ + @AddAsync + func d(a: Int, for b: String, _ value: Double, completionBlock: @escaping (Bool) -> Void) -> Void { + completionBlock(true) + } + """, + expandedSource: """ + func d(a: Int, for b: String, _ value: Double, completionBlock: @escaping (Bool) -> Void) -> Void { + completionBlock(true) + } + + func d(a: Int, for b: String, _ value: Double) async -> Bool { + await withCheckedContinuation { continuation in + d(a: a, for: b, value) { returnValue in + + continuation.resume(returning: returnValue) + + } + } + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionOnStoredPropertyEmitsError() { + assertMacroExpansion( + """ + struct Test { + @AddAsync + var name: String + } + """, + expandedSource: """ + struct Test { + var name: String + } + """, + diagnostics: [ + DiagnosticSpec( + message: "@addAsync only works on functions", + line: 2, + column: 3, + severity: .error + ) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionOnAsyncFunctionEmitsError() { + assertMacroExpansion( + """ + struct Test { + @AddAsync + async func sayHello() { + } + } + """, + expandedSource: """ + struct Test { + async func sayHello() { + } + } + """, + diagnostics: [ + DiagnosticSpec( + message: "@addAsync requires an function that returns void", + line: 2, + column: 3, + severity: .error + ) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } +} diff --git a/Examples/Tests/MacroExamples/Implementation/Peer/AddCompletionHandlerMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/Peer/AddCompletionHandlerMacroTests.swift new file mode 100644 index 00000000000..6c17a7954fa --- /dev/null +++ b/Examples/Tests/MacroExamples/Implementation/Peer/AddCompletionHandlerMacroTests.swift @@ -0,0 +1,101 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import MacroExamplesImplementation +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest + +final class AddCompletionHandlerMacroTests: XCTestCase { + private let macros = ["AddCompletionHandler": AddCompletionHandlerMacro.self] + + func testExpansionTransformsAsyncFunctionToCompletion() { + assertMacroExpansion( + """ + @AddCompletionHandler + func f(a: Int, for b: String, _ value: Double) async -> String { + return b + } + """, + expandedSource: """ + func f(a: Int, for b: String, _ value: Double) async -> String { + return b + } + + func f(a: Int, for b: String, _ value: Double, completionHandler: @escaping (String) -> Void) { + Task { + completionHandler(await f(a: a, for: b, value)) + } + } + """, + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionOnStoredPropertyEmitsError() { + assertMacroExpansion( + """ + struct Test { + @AddCompletionHandler + var value: Int + } + """, + expandedSource: """ + struct Test { + var value: Int + } + """, + diagnostics: [ + DiagnosticSpec( + message: "@addCompletionHandler only works on functions", + line: 2, + column: 3, + severity: .error + ) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } + + func testExpansionOnNonAsyncFunctionEmitsErrorWithFixItSuggestion() { + assertMacroExpansion( + """ + struct Test { + @AddCompletionHandler + func fetchData() -> String { + return "Hello, World!" + } + } + """, + expandedSource: """ + struct Test { + func fetchData() -> String { + return "Hello, World!" + } + } + """, + diagnostics: [ + DiagnosticSpec( + message: "can only add a completion-handler variant to an 'async' function", + line: 3, + column: 3, + severity: .error, + fixIts: [FixItSpec(message: "add 'async'")] + ) + ], + macros: macros, + indentationWidth: .spaces(2) + ) + } +} From 919ae855cc51133411f2f103003fc6904ee13e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ba=CC=A8k?= Date: Fri, 8 Sep 2023 18:46:03 +0200 Subject: [PATCH 5/5] Standardize indentation and code formatting in macro examples --- .../DictionaryIndirectionMacro.swift | 14 ++--- .../Implementation/Member/CustomCodable.swift | 8 +-- .../Implementation/Member/MetaEnumMacro.swift | 39 ++++++------ .../Implementation/Peer/AddAsyncMacro.swift | 15 +++-- .../ExpressionMacrosPlayground.swift | 2 +- .../DictionaryIndirectionMacroTests.swift | 16 ++--- .../ComplexMacros/OptionSetMacroTests.swift | 12 ++-- .../Member/CustomCodableTests.swift | 12 ++-- .../Member/MetaEnumMacroTests.swift | 60 +++++++++---------- .../Peer/AddAsyncMacroTests.swift | 14 ++--- 10 files changed, 93 insertions(+), 99 deletions(-) diff --git a/Examples/Sources/MacroExamples/Implementation/ComplexMacros/DictionaryIndirectionMacro.swift b/Examples/Sources/MacroExamples/Implementation/ComplexMacros/DictionaryIndirectionMacro.swift index 9297cb5bde3..23d7ce421bc 100644 --- a/Examples/Sources/MacroExamples/Implementation/ComplexMacros/DictionaryIndirectionMacro.swift +++ b/Examples/Sources/MacroExamples/Implementation/ComplexMacros/DictionaryIndirectionMacro.swift @@ -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 + } """, ] } diff --git a/Examples/Sources/MacroExamples/Implementation/Member/CustomCodable.swift b/Examples/Sources/MacroExamples/Implementation/Member/CustomCodable.swift index fc5463f94cc..1b4fffeb0b2 100644 --- a/Examples/Sources/MacroExamples/Implementation/Member/CustomCodable.swift +++ b/Examples/Sources/MacroExamples/Implementation/Member/CustomCodable.swift @@ -44,11 +44,9 @@ public enum 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] diff --git a/Examples/Sources/MacroExamples/Implementation/Member/MetaEnumMacro.swift b/Examples/Sources/MacroExamples/Implementation/Member/MetaEnumMacro.swift index 780a1f2cb37..4120d050b07 100644 --- a/Examples/Sources/MacroExamples/Implementation/Member/MetaEnumMacro.swift +++ b/Examples/Sources/MacroExamples/Implementation/Member/MetaEnumMacro.swift @@ -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) - } - } + } + } """ } } diff --git a/Examples/Sources/MacroExamples/Implementation/Peer/AddAsyncMacro.swift b/Examples/Sources/MacroExamples/Implementation/Peer/AddAsyncMacro.swift index 2a62b999668..da9faa690ba 100644 --- a/Examples/Sources/MacroExamples/Implementation/Peer/AddAsyncMacro.swift +++ b/Examples/Sources/MacroExamples/Implementation/Peer/AddAsyncMacro.swift @@ -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 = @@ -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" : "()"))") } } diff --git a/Examples/Sources/MacroExamples/Playground/ExpressionMacrosPlayground.swift b/Examples/Sources/MacroExamples/Playground/ExpressionMacrosPlayground.swift index 1fea4704133..17d9fcf897a 100644 --- a/Examples/Sources/MacroExamples/Playground/ExpressionMacrosPlayground.swift +++ b/Examples/Sources/MacroExamples/Playground/ExpressionMacrosPlayground.swift @@ -45,7 +45,7 @@ func runExpressionMacrosPlayground() { // malformed an error is emitted. Otherwise a non-optional URL is expanded. print(#URL("https://swift.org/")) - let domain = "domain.com" + // let domain = "domain.com" //print(#URL("https://\(domain)/api/path")) // error: #URL requires a static string literal //print(#URL("https://not a url.com")) // error: Malformed url diff --git a/Examples/Tests/MacroExamples/Implementation/ComplexMacros/DictionaryIndirectionMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/ComplexMacros/DictionaryIndirectionMacroTests.swift index 0b2b69dee94..03191b51563 100644 --- a/Examples/Tests/MacroExamples/Implementation/ComplexMacros/DictionaryIndirectionMacroTests.swift +++ b/Examples/Tests/MacroExamples/Implementation/ComplexMacros/DictionaryIndirectionMacroTests.swift @@ -34,19 +34,19 @@ final class DictionaryStorageMacroTests: XCTestCase { struct Point { var x: Int = 1 { get { - _storage["x", default: 1] as! Int - } + _storage["x", default: 1] as! Int + } set { - _storage["x"] = newValue - } + _storage["x"] = newValue + } } var y: Int = 2 { get { - _storage["y", default: 2] as! Int - } + _storage["y", default: 2] as! Int + } set { - _storage["y"] = newValue - } + _storage["y"] = newValue + } } var _storage: [String: Any] = [:] diff --git a/Examples/Tests/MacroExamples/Implementation/ComplexMacros/OptionSetMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/ComplexMacros/OptionSetMacroTests.swift index 0f09a656253..7ed8db52f60 100644 --- a/Examples/Tests/MacroExamples/Implementation/ComplexMacros/OptionSetMacroTests.swift +++ b/Examples/Tests/MacroExamples/Implementation/ComplexMacros/OptionSetMacroTests.swift @@ -59,16 +59,16 @@ final class OptionSetMacroTests: XCTestCase { } static let nextDay: Self = - Self (rawValue: 1 << Options.nextDay.rawValue) + Self(rawValue: 1 << Options.nextDay.rawValue) static let secondDay: Self = - Self (rawValue: 1 << Options.secondDay.rawValue) + Self(rawValue: 1 << Options.secondDay.rawValue) static let priority: Self = - Self (rawValue: 1 << Options.priority.rawValue) + Self(rawValue: 1 << Options.priority.rawValue) static let standard: Self = - Self (rawValue: 1 << Options.standard.rawValue) + Self(rawValue: 1 << Options.standard.rawValue) } extension ShippingOptions: OptionSet { @@ -110,10 +110,10 @@ final class OptionSetMacroTests: XCTestCase { } public static let nextDay: Self = - Self (rawValue: 1 << Options.nextDay.rawValue) + Self(rawValue: 1 << Options.nextDay.rawValue) public static let standard: Self = - Self (rawValue: 1 << Options.standard.rawValue) + Self(rawValue: 1 << Options.standard.rawValue) } """, macros: macros, diff --git a/Examples/Tests/MacroExamples/Implementation/Member/CustomCodableTests.swift b/Examples/Tests/MacroExamples/Implementation/Member/CustomCodableTests.swift index 1ad4d8b9b49..ea0f14f5b37 100644 --- a/Examples/Tests/MacroExamples/Implementation/Member/CustomCodableTests.swift +++ b/Examples/Tests/MacroExamples/Implementation/Member/CustomCodableTests.swift @@ -33,9 +33,9 @@ final class CustomCodableTests: XCTestCase { let age: Int enum CodingKeys: String, CodingKey { - case name - case age - } + case name + case age + } } """, macros: macros, @@ -62,9 +62,9 @@ final class CustomCodableTests: XCTestCase { func randomFunction() {} enum CodingKeys: String, CodingKey { - case name - case age = "user_age" - } + case name + case age = "user_age" + } } """, macros: macros, diff --git a/Examples/Tests/MacroExamples/Implementation/Member/MetaEnumMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/Member/MetaEnumMacroTests.swift index 305bdbb6428..e7035fb9880 100644 --- a/Examples/Tests/MacroExamples/Implementation/Member/MetaEnumMacroTests.swift +++ b/Examples/Tests/MacroExamples/Implementation/Member/MetaEnumMacroTests.swift @@ -36,24 +36,23 @@ final class MetaEnumMacroTests: XCTestCase { case null enum Meta { - case integer - case text - case boolean - case null - - init(_ __macro_local_6parentfMu_: Cell) { - switch __macro_local_6parentfMu_ { - case .integer: - self = .integer - case .text: - self = .text - case .boolean: - self = .boolean - case .null: - self = .null - } - } + case integer + case text + case boolean + case null + init(_ __macro_local_6parentfMu_: Cell) { + switch __macro_local_6parentfMu_ { + case .integer: + self = .integer + case .text: + self = .text + case .boolean: + self = .boolean + case .null: + self = .null + } } + } } """, macros: macros, @@ -77,21 +76,20 @@ final class MetaEnumMacroTests: XCTestCase { case boolean(Bool) public enum Meta { - case integer - case text - case boolean - - public init(_ __macro_local_6parentfMu_: Cell) { - switch __macro_local_6parentfMu_ { - case .integer: - self = .integer - case .text: - self = .text - case .boolean: - self = .boolean - } - } + case integer + case text + case boolean + public init(_ __macro_local_6parentfMu_: Cell) { + switch __macro_local_6parentfMu_ { + case .integer: + self = .integer + case .text: + self = .text + case .boolean: + self = .boolean + } } + } } """, macros: macros, diff --git a/Examples/Tests/MacroExamples/Implementation/Peer/AddAsyncMacroTests.swift b/Examples/Tests/MacroExamples/Implementation/Peer/AddAsyncMacroTests.swift index 16893bdb095..89227d3bda8 100644 --- a/Examples/Tests/MacroExamples/Implementation/Peer/AddAsyncMacroTests.swift +++ b/Examples/Tests/MacroExamples/Implementation/Peer/AddAsyncMacroTests.swift @@ -36,12 +36,11 @@ final class AddAsyncMacroTests: XCTestCase { c(a: a, for: b, value) { returnValue in switch returnValue { - case .success(let value): - continuation.resume(returning: value) - case .failure(let error): - continuation.resume(throwing: error) - } - + case .success(let value): + continuation.resume(returning: value) + case .failure(let error): + continuation.resume(throwing: error) + } } } } @@ -68,8 +67,7 @@ final class AddAsyncMacroTests: XCTestCase { await withCheckedContinuation { continuation in d(a: a, for: b, value) { returnValue in - continuation.resume(returning: returnValue) - + continuation.resume(returning: returnValue) } } }