A Swift macro for making inline copies of a struct by modifying a property.
The syntax is similar to Kotlin's copy function for data classes: https://kotlinlang.org/docs/data-classes.html#copying
Apply the @Copyable
macro to a struct:
@Copyable
struct Person {
let name: String
var age: Int
}
and it will add a copy function for each stored property and constant:
struct Person {
let name: String
var age: Int
/// Returns a copy of the caller whose value for `name` is different.
func copy(name: String) -> Self {
.init(name: name, age: age)
}
/// Returns a copy of the caller whose value for `age` is different.
func copy(age: Int) -> Self {
.init(name: name, age: age)
}
}
To make a copy of a struct and modify multiple properties, you can chain the copy
calls like this:
Person(name: "Walter White", age: 50).copy(age: 52).copy(name: "Heisenberg")
This is different than Kotlin's version of copy
, which allows multiple parameters to pass in a single call.
It's not possible to implement it like that in Swift because it's not possible to have default values for parameters which refer to the current values of the properties of the struct (or class).
And we also can't use nil as a marker for the old/current value, because nil might be a valid new value that we want the property to set to when we make a copy.
There might be a way that I'm not aware of, to still make it possible. So if you know how to do it, please let me know.
With version 2.1.0, the separate macro CopyableCombi was introduced, which generates copy functions with all combinations of parameters. This solution has the disadvantage that the number of generated functions can become large quickly, but it provides an API which is more similar to Kotlin's copy function.
A copy function will be generated for each stored property (var
) and each constant (let
) of the struct.
The macro recognizes computed properties by checking if they have get
or set
accessors.
This macro works only for structs.
It doesn't make sense for enums because enums can't have stored properties.
Classes and actors have reference semantics and I don't want this library to provide a copy function for reference types. I just want to augment the natural copy capability of structs with modified properties.
This macro emits a Diagnostic Message when you try to apply it to anything but a struct.
The generated copy functions produce the copies by calling the synthesized default initializer.
So, if you provide a custom initializer, no default initializer will be synthesized and the copy functions won't work.
You can still have synthesized default initializers if you define your custom initializers in an extension.
You can't apply this macro on an extension of a struct.
This seems to be a limitation of the macro system.
If you know how to make it possible, please let me know :)
Macros are a new language feature of Swift 5.9 and it will only work in Xcode 15 and later.
Add the url https://github.com/WilhelmOks/ModifiedCopyMacro.git
as a Swift Package to your project.
When prompted, select the Package Product ModifiedCopy
(Kind: Library) to add to your target.
Make a clean build.
import ModifiedCopy
in the file where you want to attach the @Copyable
macro.