A Swift macro package that generates the boilerplate for a “definition / instance / override / resolved” data-model pattern. Mark a struct with @Resolvable and annotate any fields you want to be overridable with the @Overridable property wrapper. The macro synthesizes strongly-typed nested types and a resolver so you can merge definitions, per-definition overrides, and ad-hoc instances into a unified “resolved” view model.
- Zero runtime magic: the macro generates plain Swift you can read.
- Clear type boundaries:
Definition,Instance,Override,Resolved,Source, andResolver. - Safety by construction: you cannot instantiate the base type directly (see “About the ghost initializer” below).
- Swift 5.9+ (macros)
- Xcode 16+ (for Apple platforms)
In Xcode: File > Add Packages… and paste the repository URL.
import Resolvable
@Resolvable
struct Product {
@Overridable var title: String
var sku: String
@Overridable var price: Decimal
var isActive: Bool
// Nested leaf override: explicit root + explicit leaf type
@Overridable(\Shipping.carrier, as: String.self)
var shipping: Shipping
}
struct Shipping: Codable, Hashable, Equatable {
var weight: Double
var carrier: String
}The macro synthesizes:
Product.Definition: canonical shape for definitions (Identifiable,Codable,Hashable).Product.Instance: ad‑hoc shape for instances (Identifiable,Codable,Hashable).Product.Override: optional overrides only for@Overridablefields (Identifiable,Codable,Hashable).Product.Source: provenance of a resolved item (Hashable).Product.Resolved: the read-model with merged values (Identifiable,Hashable).Product.Resolver: performs the merge.
For the Product above, Override includes:
- Whole-property overrides for
titleandprice:title: String?price: Decimal?
- Nested leaf override for
shipping.carrier, emitted asshipping_carrier: String?
public struct Product.Override: Identifiable, Codable, Hashable {
public let definitionID: UUID
public var id: UUID { definitionID }
public var title: String?
public var price: Decimal?
public var shipping_carrier: String?
public init(definitionID: UUID,
title: String? = nil,
price: Decimal? = nil,
shipping_carrier: String? = nil) {
self.definitionID = definitionID
self.title = title
self.price = price
self.shipping_carrier = shipping_carrier
}
}- Definitions:
- For each
@Overridablefield, if an override exists, use it; otherwise use the definition’s value. - For nested overrides, the macro mutates a copy of the nested struct (no need to call its memberwise init).
- For each
- Instances:
- Passed through as-is (no overrides applied).
- The result preserves provenance via
Product.Source.
Accepted forms:
- Whole property:
@Overridable var title: String
- Nested leaf (explicit root and explicit leaf type are required):
@Overridable(\Root.leaf, as: LeafType.self)- Example:
@Overridable(\Shipping.carrier, as: String.self)
Rules:
- The key path must use an explicit root (e.g.
\Shipping.carrier). Rootless paths like\.carrierare rejected with a diagnostic. - The
as:leaf type is required. This keepsOverridestrongly typed and able to synthesizeCodable/Hashable/Equatable. - Nested override fields are emitted as
parent_leaf(e.g.shipping_carrier).
Diagnostics:
- Missing key path: “@Overridable requires a key-path (e.g. @Overridable(\Shipping.carrier, as: String.self))”
- Rootless key path: “Use an explicit root in key path (e.g. \Shipping.carrier). Rootless paths (.carrier) are not allowed.”
- Missing leaf type: “Provide leaf type with ‘as: .self’”
-
struct <Base>.Definition:Identifiable,Codable,Hashablevar id: UUID = UUID()- Stored properties cloned from the base type (wrapper removed).
-
struct <Base>.Instance:Identifiable,Codable,Hashablevar id: UUID = UUID()- Same property set as
Definition.
-
struct <Base>.Override:Identifiable,Codable,Hashablelet definitionID: UUID- Optional properties only for fields marked
@Overridable - Nested leafs emitted as
parent_leaf: <LeafType>? var id: UUID { definitionID }
-
enum <Base>.Source:Hashable.definition(definitionID: UUID)and.instance(instanceID: UUID)
-
struct <Base>.Resolved:Identifiable,Hashablelet source: Source- All properties as
var(even if the original waslet) var id: UUIDderived fromsource
-
struct <Base>.Resolverstatic func resolve(definitions: [Definition], overrides: [Override], instances: [Instance]) -> [Resolved]- Applies whole-property overrides directly, and nested overrides by mutating a local copy of the nested struct.
The macro injects an initializer on the base type that is annotated as unavailable and calls fatalError, and marks any user-declared initializers unavailable as well. This intentionally prevents calling the base type’s memberwise initializer and guides you to use the nested types instead. For background on Swift’s memberwise initializer behavior, see the glossary entry on the “Memberwise initializer” hackingwithswift.com.
Use these instead:
YourType.Definitionfor canonical dataYourType.Instancefor ad-hoc itemsYourType.Overridefor per-definition overrides
Definition,Instance, andOverrideconform toCodableandHashable.- Nested leaf fields must have concrete types that are themselves
Codable/Hashable. If you introduce a non‑codable leaf type, you’ll need to removeCodablefromOverride(or add custom encoding). - Be aware of general
Codablecaveats when mixing synthesized coding with defaulted or computed members (see discussion and workarounds in Apple developer forums) developer.apple.com.
@Resolvableapplies tostructtypes only.- Only stored properties without accessors are included. Members with accessor blocks are ignored.
- Macros operate on syntax, not types; the macro requires you to specify the leaf type with
as: <Type>.selffor nested leaf overrides. Resolvedis notCodableby default. If you need it to be codable, you can fork and add conformance to the generated type, but consider how to encodesource.
- Why explicit
as: <Type>.selffor nested leafs?- To keep generated
Overridefields strongly typed and codable/hashable without fragile type inference.
- To keep generated
- Can I shorten
as: String.selffurther?- You can add your own helper (in your app/library) such as:
postfix operator ^ public postfix func ^<Root, Value>(kp: KeyPath<Root, Value>) -> Value.Type { Value.self } // @Overridable(\Shipping.carrier, as: (\Shipping.carrier)^)
- We keep the macro’s requirement explicit and stable.
- You can add your own helper (in your app/library) such as:
MIT