Skip to content

Commit f222d37

Browse files
initial copyable
1 parent 69449f7 commit f222d37

File tree

11 files changed

+1282
-0
lines changed

11 files changed

+1282
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.DS_Store
2+
13
# Xcode
24
#
35
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.resolved

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"originHash" : "8d9aa6d1ffb40aaa702c8b3825eb1a9dd7101dbed3d6781ef954028fa38aed6f",
3+
"pins" : [
4+
{
5+
"identity" : "swift-syntax",
6+
"kind" : "remoteSourceControl",
7+
"location" : "https://github.com/apple/swift-syntax.git",
8+
"state" : {
9+
"revision" : "2bc86522d115234d1f588efe2bcb4ce4be8f8b82",
10+
"version" : "510.0.3"
11+
}
12+
}
13+
],
14+
"version" : 3
15+
}

Package.swift

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//Copyright © 2024 Hootsuite Media Inc. All rights reserved.
2+
3+
// swift-tools-version: 6.0
4+
// The swift-tools-version declares the minimum version of Swift required to build this package.
5+
6+
import PackageDescription
7+
import CompilerPluginSupport
8+
9+
let package = Package(
10+
name: "Macros",
11+
platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .macCatalyst(.v13)],
12+
products: [
13+
// Products define the executables and libraries a package produces, making them visible to other packages.
14+
.library(
15+
name: "Macros",
16+
targets: ["Macros"]
17+
),
18+
.executable(
19+
name: "MacrosClient",
20+
targets: ["MacrosClient"]
21+
),
22+
],
23+
dependencies: [
24+
.package(url: "https://github.com/apple/swift-syntax.git", "509.0.0"..<"600.0.0"),
25+
],
26+
targets: [
27+
// Targets are the basic building blocks of a package, defining a module or a test suite.
28+
// Targets can depend on other targets in this package and products from dependencies.
29+
// Macro implementation that performs the source transformation of a macro.
30+
.macro(
31+
name: "MacrosImplementation",
32+
dependencies: [
33+
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
34+
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
35+
]
36+
),
37+
38+
// Library that exposes a macro as part of its API, which is used in client programs.
39+
.target(name: "Macros", dependencies: ["MacrosImplementation"]),
40+
41+
// A client of the library, which is able to use the macro in its own code.
42+
.executableTarget(name: "MacrosClient", dependencies: ["Macros"]),
43+
44+
// A test target used to develop the macro implementation.
45+
.testTarget(
46+
name: "MacrosTests",
47+
dependencies: [
48+
"MacrosImplementation",
49+
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
50+
]
51+
),
52+
]
53+
)

README.md

+41
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,43 @@
11
# copyable-macro
22
Copyable is a Swift Macro used to bring Kotlin's `copy` functionality to Swift.
3+
4+
5+
## Functionality
6+
Creates a new instance of the struct with all properties copied from the original, allowing selective modification of specific properties while keeping others unchanged
7+
8+
## Swift Package Manager
9+
In `Package.swift`:
10+
11+
```swift
12+
dependencies: [
13+
.package(url: "https://github.com/hootsuite/copyable-macro.git", from: "1.0.0")
14+
]
15+
```
16+
17+
## Usage
18+
19+
1.
20+
21+
```swift
22+
import Copyable
23+
24+
@Copyable
25+
struct Student {
26+
let name: String
27+
let grade: Int
28+
}
29+
30+
let student1 = Student(name: "Matthew", grade: 100)
31+
32+
print("name: \(student1.name) grade: \(student1.grade))
33+
34+
// should print: "name: Matthew grade: 100"
35+
36+
let student 2 = student1.copy { student in
37+
student.name = "Henry"
38+
}
39+
40+
print("name: \(student2.name) grade: \(student2.grade))
41+
42+
// should print: "name: Henry grade: 100"
43+
```

Sources/Macros/Macros.swift

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//Copyright © 2024 Hootsuite Media Inc. All rights reserved.
2+
3+
/// A macro that creates an extension on a struct to give a kotlin-like copy functionality
4+
/// source code from: https://shopify.engineering/kotlin-style-copy-function-swift-structs
5+
/// Used to make a copy of the struct while updating values passed in as parameters into a closure that receives the builder as the sole argument, and allows you to set overrides for selected properties.
6+
@attached(extension, conformances: Equatable, names: arbitrary)
7+
public macro Copyable() = #externalMacro(module: "MacrosImplementation", type: "CopyableMacro")

Sources/MacrosClient/main.swift

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//Copyright © 2024 Hootsuite Media Inc. All rights reserved.
2+
3+
import Copyable
4+
5+
//MARK: @Copyable example
6+
7+
@Copyable
8+
struct Student {
9+
let name: String
10+
let grade: Int
11+
12+
// if default params are to be used, must create custom init methods like below
13+
init(
14+
name: String = "1",
15+
grade: Int = 2
16+
) {
17+
self.name = name
18+
self.grade = grade
19+
}
20+
}
21+
22+
let student1 = Student(name: "Matt", grade: 100)
23+
let student2 = student1.copy { $0.name = "Borys" }
24+
25+
print("\(student1.name) has a grade of \(student1.grade)")
26+
print("\(student2.name) has a grade of \(student2.grade)")
27+
28+
// Copyable can also be used with vars
29+
30+
@Copyable
31+
struct Hotel {
32+
var name: String
33+
var foundingYear: Int
34+
}
35+
36+
let hotel = Hotel(name: "Hootsuite Inn", foundingYear: 2021)
37+
let newHotel = hotel.copy { $0.name = "Hootsuite Tower" }
38+
39+
print("\(hotel.name) was founded in \(hotel.foundingYear)")
40+
print("\(newHotel.name) was founded in \(newHotel.foundingYear)")
41+

0 commit comments

Comments
 (0)