Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

@OptionSet macro: Choose Your Own Adventure edition #19

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions MacroExamples.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
BD841F82294CE1F600DA4D81 /* AddBlocker.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD841F81294CE1F600DA4D81 /* AddBlocker.swift */; };
BD8A3130294947BD00E83EB9 /* Macros.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8A312F294947BD00E83EB9 /* Macros.swift */; };
BD8A31312949480600E83EB9 /* libMacroExamplesLib.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = BD8A3126294947A100E83EB9 /* libMacroExamplesLib.dylib */; };
BDB8C31129C26857000B4E7E /* OptionSetMacros.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB8C31029C26857000B4E7E /* OptionSetMacros.swift */; };
BDC559D629B857DF00F26DFF /* BitfieldMacro.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC559D529B857DF00F26DFF /* BitfieldMacro.swift */; };
BDF5AFE42947E5B000FA119B /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF5AFE32947E5B000FA119B /* main.swift */; };
BDF5AFF82947E95C00FA119B /* StringifyMacro.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF5AFF72947E95C00FA119B /* StringifyMacro.swift */; };
BDFB14B52948484000708DA6 /* MacroExamplesPluginTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFB14B42948484000708DA6 /* MacroExamplesPluginTest.swift */; };
Expand Down Expand Up @@ -97,6 +99,8 @@
BD841F81294CE1F600DA4D81 /* AddBlocker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddBlocker.swift; sourceTree = "<group>"; };
BD8A3126294947A100E83EB9 /* libMacroExamplesLib.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libMacroExamplesLib.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
BD8A312F294947BD00E83EB9 /* Macros.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Macros.swift; sourceTree = "<group>"; };
BDB8C31029C26857000B4E7E /* OptionSetMacros.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionSetMacros.swift; sourceTree = "<group>"; };
BDC559D529B857DF00F26DFF /* BitfieldMacro.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitfieldMacro.swift; sourceTree = "<group>"; };
BDF5AFE02947E5B000FA119B /* MacroExamples */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = MacroExamples; sourceTree = BUILT_PRODUCTS_DIR; };
BDF5AFE32947E5B000FA119B /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
BDF5AFEE2947E61100FA119B /* libMacroExamplesPlugin.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libMacroExamplesPlugin.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -159,6 +163,7 @@
1D682A95299E3313006F9F78 /* CustomCodable.swift */,
BD48319229AFF87200F3123A /* OptionSetMacro.swift */,
88E54A5129B5475400252D99 /* MetaEnumMacro.swift */,
BDC559D529B857DF00F26DFF /* BitfieldMacro.swift */,
);
path = MacroExamplesPlugin;
sourceTree = "<group>";
Expand All @@ -167,6 +172,7 @@
isa = PBXGroup;
children = (
BD8A312F294947BD00E83EB9 /* Macros.swift */,
BDB8C31029C26857000B4E7E /* OptionSetMacros.swift */,
31757820298DC4AF00D79290 /* NewType.swift */,
);
path = MacroExamplesLib;
Expand Down Expand Up @@ -382,6 +388,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
BDB8C31129C26857000B4E7E /* OptionSetMacros.swift in Sources */,
BD8A3130294947BD00E83EB9 /* Macros.swift in Sources */,
31757821298DC4AF00D79290 /* NewType.swift in Sources */,
);
Expand All @@ -404,6 +411,7 @@
1D682A94299E2FFB006F9F78 /* CodableKey.swift in Sources */,
371A6719299C241F00E74A8A /* CaseDetectionMacro.swift in Sources */,
BD752BE5294D3BEC00D00A2E /* WarningMacro.swift in Sources */,
BDC559D629B857DF00F26DFF /* BitfieldMacro.swift in Sources */,
3175781D298DBC8700D79290 /* NewTypeMacro.swift in Sources */,
BDF5AFF82947E95C00FA119B /* StringifyMacro.swift in Sources */,
EC21BDEB298D9F9900D585C6 /* ObservableMacro.swift in Sources */,
Expand Down Expand Up @@ -611,7 +619,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
OTHER_SWIFT_FLAGS = "-Xfrontend -enable-experimental-feature -Xfrontend Macros -Xfrontend -load-plugin-library -Xfrontend ${BUILD_DIR}/${CONFIGURATION}/libMacroExamplesPlugin.dylib -Xfrontend -dump-macro-expansions";
OTHER_SWIFT_FLAGS = "-Xfrontend -load-plugin-library -Xfrontend ${BUILD_DIR}/${CONFIGURATION}/libMacroExamplesPlugin.dylib -diagnostic-style swift";
PRODUCT_NAME = "$(TARGET_NAME)";
"SWIFT_OPTIMIZATION_LEVEL[arch=*]" = "-Onone";
SWIFT_VERSION = 5.0;
Expand All @@ -622,7 +630,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
OTHER_SWIFT_FLAGS = "-Xfrontend -enable-experimental-feature -Xfrontend Macros -Xfrontend -load-plugin-library -Xfrontend ${BUILD_DIR}/${CONFIGURATION}/libMacroExamplesPlugin.dylib -Xfrontend -dump-macro-expansions";
OTHER_SWIFT_FLAGS = "-Xfrontend -load-plugin-library -Xfrontend ${BUILD_DIR}/${CONFIGURATION}/libMacroExamplesPlugin.dylib -diagnostic-style swift";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
};
Expand Down
35 changes: 28 additions & 7 deletions MacroExamples/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,20 +167,41 @@ let jsonDecoder = JSONDecoder()
let product = try jsonDecoder.decode(CustomCodableString.self, from: json)
print(product.propertyWithOtherName)


@MyOptionSet<UInt8>
struct ShippingOptions {
private enum Options: Int {
case nextDay
case secondDay
case priority
case standard
}
static var nextDay: ShippingOptions
static var secondDay: ShippingOptions
static var priority: ShippingOptions
static var standard: ShippingOptions

static let express: ShippingOptions = [.nextDay, .secondDay]
static let all: ShippingOptions = [.express, .priority, .standard]
}

@MyOptionSet<UInt8>
struct ShippingOptionsNestedOptionsEnum {
private enum Options: UInt8 {
case nextDay, secondDay
case priority, standard
}

static let express: ShippingOptionsNestedOptionsEnum = [.nextDay, .secondDay]
static let all: ShippingOptionsNestedOptionsEnum = [.express, .priority, .standard]
}

@MyOptionSet<UInt8>
enum ShippingOptionsNestedOptionSet {
case nextDay, secondDay
case priority, standard
}

#if false
// FIXME: Currently triggers a compilation error
extension ShippingOptionsNestedOptionSet.Set {
static let express: Self = [.nextDay, .secondDay]
static let all: Self = [.express, .priority, .standard]
}
#endif

// `@MetaEnum` adds a nested enum called `Meta` with the same cases, but no
// associated values/payloads. Handy for e.g. describing a schema.
Expand Down
31 changes: 1 addition & 30 deletions MacroExamplesLib/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ public struct ObservationRegistrar<Subject: Observable> {
}

@attached(member, names: named(Storage), named(_storage), named(_registrar), named(addObserver), named(removeObserver), named(withTransaction))
@attached(memberAttribute)
public macro Observable() = #externalMacro(module: "MacroExamplesPlugin", type: "ObservableMacro")

@attached(accessor)
Expand All @@ -106,33 +105,5 @@ public macro MetaEnum() = #externalMacro(module: "MacroExamplesPlugin", type: "M
@attached(member)
public macro CodableKey(name: String) = #externalMacro(module: "MacroExamplesPlugin", type: "CodableKey")

@attached(member, names: named(CodingKeys))
public macro CustomCodable() = #externalMacro(module: "MacroExamplesPlugin", type: "CustomCodable")

/// Create an option set from a struct that contains a nested `Options` enum.
///
/// Attach this macro to a struct that contains a nested `Options` enum
/// with an integer raw value. The struct will be transformed to conform to
/// `OptionSet` by
/// 1. Introducing a `rawValue` stored property to track which options are set,
/// along with the necessary `RawType` typealias and initializers to satisfy
/// the `OptionSet` protocol.
/// 2. Introducing static properties for each of the cases within the `Options`
/// enum, of the type of the struct.
///
/// The `Options` enum must have a raw value, where its case elements
/// each indicate a different option in the resulting option set. For example,
/// the struct and its nested `Options` enum could look like this:
///
/// @MyOptionSet
/// struct ShippingOptions {
/// private enum Options: Int {
/// case nextDay
/// case secondDay
/// case priority
/// case standard
/// }
/// }
@attached(member, names: arbitrary)
@attached(conformance)
public macro MyOptionSet<RawType>() = #externalMacro(module: "MacroExamplesPlugin", type: "OptionSetMacro")
public macro CustomCodable() = #externalMacro(module: "MacroExamplesPlugin", type: "CustomCodable")
96 changes: 96 additions & 0 deletions MacroExamplesLib/OptionSetMacros.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/// Specifies the form of option set to which the option set macro is applied.
public enum OptionSetForm {
/// Options are described via static variables within the struct, allow
/// of which are expected to have the same type as `Self`.
/// the option set macros.
///
/// @OptionSet<UInt8>
/// struct ShippingOptions {
/// static var nextDay: ShippingOptions
/// static var secondDay: ShippingOptions
/// static var priority: ShippingOptions
/// static var standard: ShippingOptions
/// }
///
/// Any static variable that has an initializer already will be ignored.
case staticVariables

/// Options are described via the cases of a nested enum with the given
/// name.
///
/// @OptionSet<UInt8>
/// struct ShippingOptions {
/// private enum Options {
/// case nextDay
/// case secondDay
/// case priority
/// case standard
/// }
/// }
///
/// The cases will be used to indicate bit positions in the resulting
/// raw value, and the `OptionSet` macro will introduced static variables
/// of the type of the struct itself (similar to those that have are
/// written explicitly for the `staticVariables` form).
case nestedOptionsEnum(String = "Options")

/// Options are described via cases on the enum to which the option set
/// macro is applied.
///
/// @OptionSet<UInt8>
/// enum ShippingOptions {
/// case nextDay
/// case secondDay
/// case priority
/// case standard
/// }
///
/// As with `nestedEnum`, the cases provide the bit numbers for the
/// corresponding options in the raw value. With this kind, a nested
/// struct with the given name will be created that is itself an option
/// set, e.g.,
///
/// struct Set: OptionSet {
/// var rawValue: UInt8
/// static var nextDay: Set = Set(rawValue: 1 << 0)
/// static var secondDay: Set = Set(rawValue: 1 << 1)
/// static var priority: Set = Set(rawValue: 1 << 2)
/// static var standard: Set = Set(rawValue: 1 << 3)
/// }
case nestedOptionSet(String = "Set")
}

/// Create an bit-packed option set from a type that sketches the option names.
///
/// TODO: Update this
///
/// Attach this macro to a struct that contains a nested `Options` enum
/// with an integer raw value. The struct will be transformed to conform to
/// `OptionSet` by
/// 1. Introducing a `rawValue` stored property to track which options are set,
/// along with the necessary `RawType` typealias and initializers to satisfy
/// the `OptionSet` protocol. The raw type is specified after `@OptionSet`,
/// e.g., `@OptionSet<UInt8>`.
/// 2. Introducing static properties for each of the cases within the `Options`
/// enum, of the type of the struct.
///
/// The `Options` enum must have a raw value, where its case elements
/// each indicate a different option in the resulting option set. For example,
/// the struct and its nested `Options` enum could look like this:
///
/// @MyOptionSet<UInt8>
/// struct ShippingOptions {
/// private enum Options: Int {
/// case nextDay
/// case secondDay
/// case priority
/// case standard
/// }
/// }
@attached(member, names: named(RawValue), named(rawValue), named(`init`), arbitrary)
@attached(conformance)
@attached(memberAttribute)
public macro MyOptionSet<RawType>() = #externalMacro(module: "MacroExamplesPlugin", type: "OptionSetMacro")

@attached(accessor)
public macro Bitfield(bit: Int) = #externalMacro(module: "MacroExamplesPlugin", type: "BitfieldMacro")
28 changes: 28 additions & 0 deletions MacroExamplesPlugin/BitfieldMacro.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros

public struct BitfieldMacro { }

extension BitfieldMacro: AccessorMacro {
public static func expansion(
of attribute: AttributeSyntax,
providingAccessorsOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [AccessorDeclSyntax] {
guard case let .argumentList(arguments) = attribute.argument,
let bitArg = arguments.first(labeled: "bit"),
let bit = bitArg.expression.as(IntegerLiteralExprSyntax.self)?.digits else {
return []
}

return [
"""

get {
Self(rawValue: 1 << \(bit))
}
"""
]
}
}
Loading