Skip to content

Commit 115f451

Browse files
committed
Support expanded builder to confom to Sendable when original object explicitily conforms to Sendable
1 parent 8fed00f commit 115f451

9 files changed

Lines changed: 150 additions & 11 deletions

File tree

Sources/BuildableClient/main.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ enum MyEnum {
4545
}
4646

4747
@Buildable
48-
class MyClass {
49-
var m1: String = ""
48+
final class MyClass: Sendable {
49+
let m1: String
5050

5151
init(
5252
m1: String

Sources/BuildableMacro/Enum/GenerateBuilderFromEnum.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ func generateBuilderFromEnum(enumDecl: EnumDeclSyntax) throws -> StructDeclSynta
1313
type: TypeSyntax(stringLiteral: enumDecl.name.text),
1414
value: try getFirstEnumCaseName(from: enumDecl)
1515
)
16-
17-
return StructDeclSyntax(name: getStructBuilderName(from: enumDecl.name)) {
16+
17+
return StructDeclSyntax(
18+
name: getStructBuilderName(from: enumDecl.name),
19+
inheritanceClause: getSendableInheritanceClause(original: enumDecl.inheritanceClause)
20+
) {
1821
MemberBlockItemListSyntax {
1922
MemberBlockItemSyntax(decl: makeVariableDeclWithValue(enumMember: enumMember))
2023
MemberBlockItemSyntax(

Sources/BuildableMacro/StructAndClass/GenerateBuilderFromClass.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ func generateBuilderFromClass(classDecl: ClassDeclSyntax) throws -> StructDeclSy
1111
guard let initialiserDecl = getFirstInitialiser(from: classDecl.memberBlock) else {
1212
throw "Missing initialiser"
1313
}
14-
let structMembers = extractInitializerMembers(from: initialiserDecl)
15-
16-
return makeStructBuilder(withStructName: classDecl.name, and: structMembers)
14+
return makeStructBuilder(
15+
structName: classDecl.name,
16+
inheritanceClause: classDecl.inheritanceClause,
17+
structMembers: extractInitializerMembers(from: initialiserDecl)
18+
)
1719
}

Sources/BuildableMacro/StructAndClass/GenerateBuilderFromStruct.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
import SwiftSyntax
99

1010
func generateBuilderFromStruct(structDecl: StructDeclSyntax) -> StructDeclSyntax {
11-
let structMembers = getStructMembers(structDecl: structDecl)
12-
return makeStructBuilder(withStructName: structDecl.name, and: structMembers)
11+
makeStructBuilder(
12+
structName: structDecl.name,
13+
inheritanceClause: structDecl.inheritanceClause,
14+
structMembers: getStructMembers(structDecl: structDecl)
15+
)
1316
}
1417

1518
private func getStructMembers(structDecl: StructDeclSyntax) -> [StructMember] {

Sources/BuildableMacro/StructAndClass/Helper/MakeStructBuilder.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@
77

88
import SwiftSyntax
99

10-
func makeStructBuilder(withStructName structName: TokenSyntax, and structMembers: [StructMember]) -> StructDeclSyntax {
11-
StructDeclSyntax(name: getStructBuilderName(from: structName)) {
10+
func makeStructBuilder(
11+
structName: TokenSyntax,
12+
inheritanceClause: InheritanceClauseSyntax?,
13+
structMembers: [StructMember]
14+
) -> StructDeclSyntax {
15+
StructDeclSyntax(
16+
name: getStructBuilderName(from: structName),
17+
inheritanceClause: getSendableInheritanceClause(original: inheritanceClause)
18+
) {
1219
MemberBlockItemListSyntax {
1320
for structMember in structMembers {
1421
MemberBlockItemSyntax(decl: makeVariableDecl(structMember: structMember))
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// File.swift
3+
// Buildable
4+
//
5+
// Created by Alexander Schmutz on 14.05.25.
6+
//
7+
8+
import SwiftSyntax
9+
10+
func getSendableInheritanceClause(
11+
original originalInheritanceClause: InheritanceClauseSyntax?
12+
) -> InheritanceClauseSyntax? {
13+
guard let inheritedSendableType = originalInheritanceClause?.inheritedTypes.first(where: { $0.isSendableIdentifier }) else {
14+
return nil
15+
}
16+
return InheritanceClauseSyntax(
17+
colon: TokenSyntax.colonToken(leadingTrivia: .spaces(0), trailingTrivia: .space),
18+
inheritedTypes: InheritedTypeListSyntax([inheritedSendableType])
19+
)
20+
}
21+
22+
private extension InheritedTypeSyntax {
23+
var isSendableIdentifier: Bool {
24+
type.as(IdentifierTypeSyntax.self)?.name.text == "Sendable"
25+
}
26+
}

Tests/BuildableMacroTests/BuildableClassTests.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,5 +180,46 @@ class BuildableClassTests: XCTestCase {
180180
macros: testMacros
181181
)
182182
}
183+
184+
func test_should_make_class_sendable() {
185+
assertMacroExpansion(
186+
"""
187+
@Buildable
188+
class MyClass: Sendable {
189+
let m1: String
190+
191+
init(
192+
m1: String = ""
193+
) {
194+
self.m1 = m1
195+
}
196+
}
197+
""",
198+
expandedSource: """
199+
200+
class MyClass: Sendable {
201+
let m1: String
202+
203+
init(
204+
m1: String = ""
205+
) {
206+
self.m1 = m1
207+
}
208+
}
209+
210+
struct MyClassBuilder : Sendable {
211+
var m1: String = ""
212+
213+
func build() -> MyClass {
214+
return MyClass(
215+
m1: m1
216+
)
217+
}
218+
}
219+
220+
""",
221+
macros: testMacros
222+
)
223+
}
183224
}
184225

Tests/BuildableMacroTests/BuildableEnumTests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,5 +147,32 @@ class BuildableEnumTests: XCTestCase {
147147
macros: testMacros
148148
)
149149
}
150+
151+
func test_should_make_enum_sendable() {
152+
assertMacroExpansion(
153+
"""
154+
@Buildable
155+
enum MyEnum: Sendable {
156+
case myCase
157+
}
158+
""",
159+
expandedSource: """
160+
161+
enum MyEnum: Sendable {
162+
case myCase
163+
}
164+
165+
struct MyEnumBuilder : Sendable {
166+
var value: MyEnum = .myCase
167+
168+
func build() -> MyEnum {
169+
return value
170+
}
171+
}
172+
173+
""",
174+
macros: testMacros
175+
)
176+
}
150177
}
151178

Tests/BuildableMacroTests/BuildableStructTests.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,36 @@ class BuildableStructTests: XCTestCase {
433433
)
434434
}
435435

436+
func test_should_make_struct_sendable() {
437+
assertMacroExpansion(
438+
"""
439+
@Buildable
440+
struct MyObject: Sendable {
441+
let m1: () -> String
442+
}
443+
""",
444+
expandedSource: """
445+
446+
struct MyObject: Sendable {
447+
let m1: () -> String
448+
}
449+
450+
struct MyObjectBuilder : Sendable {
451+
var m1: () -> String = {
452+
return ""
453+
}
454+
455+
func build() -> MyObject {
456+
return MyObject(
457+
m1: m1
458+
)
459+
}
460+
}
461+
""",
462+
macros: testMacros
463+
)
464+
}
465+
436466
func test_should_set_default_value_for_defined_types() {
437467
assertMacroExpansion(
438468
"""

0 commit comments

Comments
 (0)