Skip to content

Commit d110ff5

Browse files
feat(minor): [sc-24016] enable isolation specification in generated equatable confomance (#29)
1 parent 63e597c commit d110ff5

19 files changed

+1938
-1031
lines changed

Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ let package = Package(
1515
)
1616
],
1717
dependencies: [
18-
.package(url: "https://github.com/swiftlang/swift-syntax.git", "600.0.0"..<"602.0.0")
18+
.package(url: "https://github.com/swiftlang/swift-syntax.git", "602.0.0" ..< "603.0.0")
1919
],
2020
targets: [
2121
// Targets are the basic building blocks of a package, defining a module or a test suite.

README.md

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,75 @@ extension ProfileView: Equatable {
7676
lhs.id == rhs.id && lhs.username == rhs.username
7777
}
7878
}
79+
```
80+
81+
## Isolation
82+
`Equatable` macro supports generating the conformance with different isolation levels by using the `isolation` parameter.
83+
The parameter accepts three values: `.nonisolated` (default), `.isolated`, and `.main` (requires Swift 6.2 or later).
84+
The chosen isolation level will be applied to the generated conformances for both `Equatable` and `Hashable` (if applicable).
85+
86+
### Nonisolated (default)
87+
The generated `Equatable` conformance is `nonisolated`, meaning it can be called from any context without isolation guarantees.
88+
```swift
89+
@Equatable(isolation: .nonisolated) (also omitting the parameter uses this mode)
90+
struct Person {
91+
let name: String
92+
let age: Int
93+
}
94+
```
95+
96+
expands to:
97+
```swift
98+
extension Person: Equatable {
99+
nonisolated public static func == (lhs: Person, rhs: Person) -> Bool {
100+
lhs.name == rhs.name && lhs.age == rhs.age
101+
}
102+
}
103+
```
104+
105+
### Isolated
106+
The generated `Equatable` conformance is `isolated`, meaning it can only be called from within the actor's context.
107+
```swift
108+
@Equatable(isolation: .isolated)
109+
struct Person {
110+
let name: String
111+
let age: Int
112+
}
113+
```
114+
115+
expands to:
116+
```swift
117+
extension Person: Equatable {
118+
public static func == (lhs: Person, rhs: Person) -> Bool {
119+
lhs.name == rhs.name && lhs.age == rhs.age
120+
}
121+
}
122+
```
123+
124+
### Main (requires Swift 6.2 or later)
125+
A common case is to have a `@MainActor` isolated type, SwiftUI views being a common example. Previously, the generated `Equatable` conformance had to be `nonisolated` in order to satisfy the protocol requirement.
126+
This would then restrict us to access only nonisolated properties of the type in the generated `Equatable` function — which meant that we had to ignore all `@MainActor` isolated properties in the equality comparison.
127+
Swift 6.2 introduced [isolated conformances](https://docs.swift.org/compiler/documentation/diagnostics/isolated-conformances/) allowing us to generate `Equatable` conformances
128+
which are bound to the `@MainActor`. In this way the generated `Equatable` conformance can access `@MainActor` isolated properties of the type synchronously and the compiler will guarantee that the conformance
129+
will be called only from the `@MainActor` context.
130+
131+
We can do so by specifying `@Equatable(isolation: .main)`, e.g:
132+
```swift
133+
@Equatable(isolation: .main)
134+
@MainActor
135+
struct Person {
136+
let name: String
137+
let age: Int
138+
}
139+
```
140+
141+
expands to:
142+
```swift
143+
extension Person: Equatable {
144+
public static func == (lhs: Person, rhs: Person) -> Bool {
145+
lhs.name == rhs.name && lhs.age == rhs.age
146+
}
147+
}
79148
```
80149

81150
## Safety Considerations
@@ -148,9 +217,8 @@ import Equatable
148217
@Equatable
149218
struct User: Hashable {
150219

151-
let id: Int
152-
153-
@EquatableIgnored var name = ""
220+
let id: Int
221+
@EquatableIgnored var name = ""
154222
}
155223
```
156224

Sources/Equatable/Equatable.swift

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,91 @@
6767
/// }
6868
/// }
6969
/// ```
70+
///
71+
///
72+
/// ## Isolation
73+
/// `Equatable` macro supports generating the conformance with different isolation levels by using the `isolation` parameter.
74+
/// The parameter accepts three values: `.nonisolated` (default), `.isolated`, and `.main` (requires Swift 6.2 or later).
75+
/// The chosen isolation level will be applied to the generated conformances for both `Equatable` and `Hashable` (if applicable).
76+
///
77+
/// ### Nonisolated (default)
78+
/// The generated `Equatable` conformance is `nonisolated`, meaning it can be called from any context without isolation guarantees.
79+
/// ```swift
80+
/// @Equatable(isolation: .nonisolated) (also ommiting the parameter uses this mode)
81+
/// struct Person {
82+
/// let name: String
83+
/// let age: Int
84+
/// }
85+
/// ```
86+
///
87+
/// expands to:
88+
/// ```swift
89+
/// extension Person: Equatable {
90+
/// nonisolated public static func == (lhs: Person, rhs: Person) -> Bool {
91+
/// lhs.name == rhs.name && lhs.age == rhs.age
92+
/// }
93+
/// }
94+
/// ```
95+
///
96+
/// ### Isolated
97+
/// The generated `Equatable` conformance is `isolated`, meaning it can only be called from within the actor's context.
98+
/// ```swift
99+
/// @Equatable(isolation: .isolated)
100+
/// struct Person {
101+
/// let name: String
102+
/// let age: Int
103+
/// }
104+
/// ```
105+
///
106+
/// expands to:
107+
/// ```swift
108+
/// extension Person: Equatable {
109+
/// public static func == (lhs: Person, rhs: Person) -> Bool {
110+
/// lhs.name == rhs.name && lhs.age == rhs.age
111+
/// }
112+
/// }
113+
/// ```
114+
///
115+
/// ### Main (requires Swift 6.2 or later)
116+
/// A common case is to have a `@MainActor` isolated type, SwiftUI views being a common example. Previously, the generated `Equatable` conformance had to be `nonisolated` in order to satisfy the protocol requirement.
117+
/// This would then restrict us to access only nonisolated properties of the type in the generated `Equatable` function — which ment that we had to ignore all `@MainActor` isolated properties in the equality comparison.
118+
/// Swift 6.2 introduced [isolated confomances](https://docs.swift.org/compiler/documentation/diagnostics/isolated-conformances/) allowing us to generate `Equatable` confomances
119+
/// which are bound to the `@MainActor`. In this way the generated `Equatable` conformance can access `@MainActor` isolated properties of the type synchonously and the compiler will guarantee that the confomance
120+
/// will be called only from the `@MainActor` context.
121+
///
122+
/// We can do so by specifying `@Equatable(isolation: .main)`, e.g:
123+
/// ```swift
124+
/// @Equatable(isolation: .main)
125+
/// @MainActor
126+
/// struct Person {
127+
/// let name: String
128+
/// let age: Int
129+
/// }
130+
/// ```
131+
///
132+
/// expands to:
133+
/// ```swift
134+
/// extension Person: Equatable {
135+
/// public static func == (lhs: Person, rhs: Person) -> Bool {
136+
/// lhs.name == rhs.name && lhs.age == rhs.age
137+
/// }
138+
/// }
139+
/// ```
140+
///
70141
@attached(extension, conformances: Equatable, Hashable, names: named(==), named(hash(into:)))
71-
public macro Equatable() = #externalMacro(module: "EquatableMacros", type: "EquatableMacro")
142+
public macro Equatable(isolation: Isolation = .nonisolated) = #externalMacro(module: "EquatableMacros", type: "EquatableMacro")
143+
144+
/// Isolation level for the generated Equatable functions.
145+
public enum Isolation {
146+
/// The generated `Equatable` conformance is `nonisolated`.
147+
case nonisolated
148+
/// The generated `Equatable` conformance is`isolated`.
149+
case isolated
150+
#if swift(>=6.2)
151+
/// The generated `Equatable` conformance is `@MainActor` isolated.
152+
case main
153+
#endif
154+
}
72155

73156
/// A peer macro that marks properties to be ignored in `Equatable` conformance generation.
74157
///

0 commit comments

Comments
 (0)