Skip to content

Commit 12f3177

Browse files
authored
feat: made initialized mutable variables initialization optional in member-wise initializers (#15)
* feat: made initialized mutable variables initialization optional in member-wise initializers - added multiple member-wise initializers based on mulatable initialized variables present * wip: use code-factor suggestions
1 parent 8d61676 commit 12f3177

23 files changed

+407
-140
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Supercharge `Swift`'s `Codable` implementations with macros.
2121
- Allows to create flattened model for nested `CodingKey` values with ``CodedAt(_:)`` and ``CodedIn(_:)``.
2222
- Allows to create composition of multiple `Codable` types with ``CodedAt(_:)`` passing no arguments.
2323
- Allows to provide default value in case of decoding failures with ``Default(_:)``.
24-
- Generates member-wise initializer considering the above default value syntax as well.
24+
- Generates member-wise initializer(s) considering the above default value syntax as well.
2525
- Allows to create custom decoding/encoding strategies with ``HelperCoder`` and using them with ``CodedBy(_:)``. i.e. ``LossySequenceCoder`` etc.
2626

2727
## Requirements
@@ -181,7 +181,7 @@ struct Coordinate {
181181
</details>
182182

183183
<details>
184-
<summary>Provide default value in case of decoding failures and member-wise initializer generated considers these default values.</summary>
184+
<summary>Provide default value in case of decoding failures and member-wise initializer(s) generated considers these default values.</summary>
185185

186186
Instead of throwing error in case of missing data or type mismatch, you can provide a default value that will be assigned in this case. The memberwise initializer generated also uses this default value for the field. The following definition with `MetaCodable`:
187187

Sources/CodableMacroPlugin/Attributes/Codable.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import SwiftSyntaxMacros
1515
/// for the attached struct declaration named `CodingKeys` and use
1616
/// this type for `Codable` implementation of both `init(from:)`
1717
/// and `encode(to:)` methods by using `CodedPropertyMacro`
18-
/// declarations. Additionally member-wise initializer is also generated.
18+
/// declarations. Additionally member-wise initializer(s) also generated.
1919
struct Codable: Attribute {
2020
/// The node syntax provided
2121
/// during initialization.
@@ -110,7 +110,7 @@ extension Codable: ConformanceMacro, MemberMacro {
110110
///
111111
/// - Returns: `CodingKeys` type and `init(from:)`, `encode(to:)`,
112112
/// method declarations for `Codable` implementation along with
113-
/// member-wise initializer declaration.
113+
/// member-wise initializer declaration(s).
114114
static func expansion(
115115
of node: AttributeSyntax,
116116
providingMembersOf declaration: some DeclGroupSyntax,
@@ -147,12 +147,7 @@ extension Codable: ConformanceMacro, MemberMacro {
147147
}
148148

149149
// generate
150-
return [
151-
DeclSyntax(registrar.memberInit(in: context)),
152-
DeclSyntax(registrar.decoding(in: context)),
153-
DeclSyntax(registrar.encoding(in: context)),
154-
DeclSyntax(registrar.codingKeys(in: context)),
155-
]
150+
return registrar.memberDeclarations(in: context)
156151
}
157152
}
158153

Sources/CodableMacroPlugin/Builders/Default+RegistrationBuilder.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
import SwiftSyntax
22

33
extension Default: RegistrationBuilder {
4+
/// The any variable data with required initialization that input registration can have.
5+
typealias Input = AnyVariable<RequiredInitialization>
46
/// The variable data with default expression that output registration will have.
5-
typealias Output = DefaultValueVariable<AnyVariable>
7+
typealias Output = DefaultValueVariable<Input>
68

79
/// Build new registration with provided input registration.
810
///
911
/// New registration is updated with default expression data that will be used
10-
/// for decoding failure and member-wise initializer, if provided.
12+
/// for decoding failure and member-wise initializer(s), if provided.
1113
///
1214
/// - Parameter input: The registration built so far.
1315
/// - Returns: Newly built registration with default expression data.
14-
func build(with input: Registration<AnyVariable>) -> Registration<Output> {
16+
func build(with input: Registration<Input>) -> Registration<Output> {
1517
let expr = node.argument!
1618
.as(TupleExprElementListSyntax.self)!.first!.expression
1719
return input.updating(with: input.variable.with(default: expr))
1820
}
1921
}
2022

21-
fileprivate extension Variable {
23+
fileprivate extension Variable where Initialization == RequiredInitialization {
2224
/// Update variable data with the default value expression provided.
2325
///
2426
/// `DefaultValueVariable` is created with this variable as base

Sources/CodableMacroPlugin/Builders/InitializationRegistrationBuilder.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import SwiftSyntax
44
///
55
/// Checks whether variable can be initialized and whether variable has been already initialized
66
/// from the current syntax and updates the registrations variable data accordingly.
7-
struct InitializationRegistrationBuilder<Input: Variable>: RegistrationBuilder {
7+
struct InitializationRegistrationBuilder<Input: Variable>: RegistrationBuilder
8+
where Input.Initialization == RequiredInitialization {
89
/// The output registration variable type that handles initialization data.
910
typealias Output = InitializationVariable<Input>
1011

@@ -33,7 +34,7 @@ struct InitializationRegistrationBuilder<Input: Variable>: RegistrationBuilder {
3334
|| input.context.binding.initializer == nil
3435
}
3536

36-
let initialized = input.context.binding.initializer == nil
37+
let initialized = input.context.binding.initializer != nil
3738
let options = Output.Options(init: canInit, initialized: initialized)
3839
let newVariable = Output(base: input.variable, options: options)
3940
return input.updating(with: newVariable)

Sources/CodableMacroPlugin/Builders/OptionalRegistrationBuilder.swift

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@
44
/// The optional registration is used to update registration data with current syntax.
55
/// If optional registration not provided, input registration is passed with variable type erased.
66
struct OptionalRegistrationBuilder<Builder>: RegistrationBuilder
7-
where Builder: RegistrationBuilder {
7+
where Builder: RegistrationBuilder,
8+
Builder.Input.Initialization == Builder.Output.Initialization {
9+
/// The variable data of underlying builder's input registration.
810
typealias Input = Builder.Input
11+
/// The variable data generated by passed builder or the input
12+
/// variable data if no builder passed.
13+
typealias Output = AnyVariable<Input.Initialization>
14+
915
/// The optional builder to use.
1016
///
1117
/// This will be used as underlying
@@ -31,9 +37,9 @@ where Builder: RegistrationBuilder {
3137
///
3238
/// - Parameter input: The registration built so far.
3339
/// - Returns: Newly built registration with additional data.
34-
func build(with input: Registration<Input>) -> Registration<AnyVariable> {
40+
func build(with input: Registration<Input>) -> Registration<Output> {
3541
let keyPath: [String]
36-
let variable: Variable
42+
let variable: any Variable<Input.Initialization>
3743
if let reg = base?.build(with: input) {
3844
keyPath = reg.keyPath
3945
variable = reg.variable

Sources/CodableMacroPlugin/Registration/Node.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,15 @@ extension Registrar {
5353
}
5454
}
5555
/// All the variables registered at this node.
56-
private(set) var variables: [Variable]
56+
private(set) var variables: [any Variable]
5757
/// Nested registration node associated with keys.
5858
private(set) var children: OrderedDictionary<Key, Self>
5959

6060
/// List of all the linked variables registered.
6161
///
6262
/// Gets all variables at current node
6363
/// and children nodes.
64-
var linkedVariables: [Variable] {
64+
var linkedVariables: [any Variable] {
6565
return variables + children.flatMap { $1.linkedVariables }
6666
}
6767

@@ -73,7 +73,7 @@ extension Registrar {
7373
///
7474
/// - Returns: The newly created node instance.
7575
init(
76-
variables: [Variable] = [],
76+
variables: [any Variable] = [],
7777
children: OrderedDictionary<Key, Self> = [:]
7878
) {
7979
self.variables = variables
@@ -90,7 +90,7 @@ extension Registrar {
9090
/// additional macro metadata.
9191
/// - keyPath: The `CodingKey` path where the value
9292
/// will be decode/encoded.
93-
mutating func register(variable: Variable, keyPath: [Key]) {
93+
mutating func register(variable: any Variable, keyPath: [Key]) {
9494
guard !keyPath.isEmpty else { variables.append(variable); return }
9595

9696
let key = keyPath.first!

Sources/CodableMacroPlugin/Registration/Registrar.swift

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ struct Registrar {
1919
/// conformance implementation declarations.
2020
fileprivate let modifiers: ModifierListSyntax?
2121

22+
/// Member-wise initialization generator with provided options.
23+
///
24+
/// Creates member-wise initialization generator by passing
25+
/// the provided access modifiers.
26+
var initGenerator: MemberwiseInitGenerator {
27+
return .init(options: .init(modifiers: modifiers))
28+
}
29+
2230
/// Creates a new options instance with provided parameters.
2331
///
2432
/// - Parameters:
@@ -82,6 +90,26 @@ struct Registrar {
8290
)
8391
}
8492

93+
/// Generates member declarations for `Codable` macro.
94+
///
95+
/// From the variables registered by `Codable` macro,
96+
/// member-wise initialization, `Codable` protocol conformance
97+
/// and `CodingKey` declarations are generated.
98+
///
99+
/// - Parameter context: The context in which to perform
100+
/// the macro expansion.
101+
///
102+
/// - Returns: The generated member declarations.
103+
func memberDeclarations(
104+
in context: some MacroExpansionContext
105+
) -> [DeclSyntax] {
106+
var decls = memberInit(in: context).map { DeclSyntax($0) }
107+
decls.append(DeclSyntax(decoding(in: context)))
108+
decls.append(DeclSyntax(encoding(in: context)))
109+
decls.append(DeclSyntax(codingKeys(in: context)))
110+
return decls
111+
}
112+
85113
/// Provides the declaration of `CodingKey` type that is used
86114
/// for `Codable` implementation generation.
87115
///
@@ -92,7 +120,7 @@ struct Registrar {
92120
/// the macro expansion.
93121
///
94122
/// - Returns: The generated enum declaration.
95-
func codingKeys(
123+
private func codingKeys(
96124
in context: some MacroExpansionContext
97125
) -> EnumDeclSyntax {
98126
return caseMap.decl(in: context)
@@ -105,7 +133,7 @@ struct Registrar {
105133
/// the macro expansion.
106134
///
107135
/// - Returns: The generated initializer declaration.
108-
func decoding(
136+
private func decoding(
109137
in context: some MacroExpansionContext
110138
) -> InitializerDeclSyntax {
111139
return InitializerDeclSyntax.decode(
@@ -125,7 +153,7 @@ struct Registrar {
125153
/// the macro expansion.
126154
///
127155
/// - Returns: The generated function declaration.
128-
func encoding(
156+
private func encoding(
129157
in context: some MacroExpansionContext
130158
) -> FunctionDeclSyntax {
131159
return FunctionDeclSyntax.encode(
@@ -138,43 +166,20 @@ struct Registrar {
138166
}
139167
}
140168

141-
/// Provides the member-wise initializer declaration.
169+
/// Provides the member-wise initializer declaration(s).
142170
///
143171
/// - Parameter context: The context in which to perform
144172
/// the macro expansion.
145173
///
146-
/// - Returns: The generated initializer declaration.
147-
func memberInit(
174+
/// - Returns: The generated initializer declarations.
175+
private func memberInit(
148176
in context: some MacroExpansionContext
149-
) -> InitializerDeclSyntax {
150-
let allVariables = root.linkedVariables
151-
var params: [FunctionParameterSyntax] = []
152-
var exprs: [CodeBlockItemSyntax] = []
153-
params.reserveCapacity(allVariables.count)
154-
exprs.reserveCapacity(allVariables.count)
155-
156-
allVariables.forEach { variable in
157-
switch variable.initializing(in: context) {
158-
case .ignored:
159-
break
160-
case .optional(let param, let expr),
161-
.required(let param, let expr):
162-
params.append(param)
163-
exprs.append(expr)
164-
}
165-
}
166-
return InitializerDeclSyntax(
167-
modifiers: options.modifiers,
168-
signature: .init(
169-
input: .init(
170-
parameterList: .init {
171-
for param in params { param }
172-
}
173-
)
174-
)
175-
) {
176-
for expr in exprs { expr }
177+
) -> [InitializerDeclSyntax] {
178+
var generator = options.initGenerator
179+
for variable in root.linkedVariables {
180+
generator = variable.initializing(in: context).add(to: generator)
177181
}
182+
return generator.declarations(in: context)
178183
}
179184
}
180185

Sources/CodableMacroPlugin/Variables/AnyVariable.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
import SwiftSyntax
22
import SwiftSyntaxMacros
33

4-
/// A type-erased variable value.
4+
/// A type-erased variable value only containing initialization type data.
55
///
6-
/// The `AnyVariable` type forwards `Variable`
7-
/// implementations to an underlying variable value,
8-
/// hiding the type of the wrapped value.
9-
struct AnyVariable: Variable {
6+
/// The `AnyVariable` type forwards `Variable` implementations to an underlying
7+
/// variable value, hiding the type of the wrapped value.
8+
struct AnyVariable<Initialization: VariableInitialization>: Variable {
109
/// The value wrapped by this instance.
1110
///
1211
/// The base property can be cast back
1312
/// to its original type using type casting
1413
/// operators (`as?`, `as!`, or `as`).
15-
let base: Variable
14+
let base: any Variable<Initialization>
1615

1716
/// The name of the variable.
1817
///
@@ -22,6 +21,11 @@ struct AnyVariable: Variable {
2221
///
2322
/// Provides type of the underlying variable value.
2423
var type: TypeSyntax { base.type }
24+
/// Whether the variable is needed for final code generation.
25+
///
26+
/// Provides whether underlying variable value is needed
27+
/// for final code generation.
28+
var canBeRegistered: Bool { base.canBeRegistered }
2529

2630
/// Indicates the initialization type for this variable.
2731
///
@@ -32,7 +36,7 @@ struct AnyVariable: Variable {
3236
/// - Returns: The type of initialization for variable.
3337
func initializing(
3438
in context: some MacroExpansionContext
35-
) -> VariableInitialization {
39+
) -> Initialization {
3640
return base.initializing(in: context)
3741
}
3842

Sources/CodableMacroPlugin/Variables/Data/VariableInitialization.swift

Lines changed: 0 additions & 57 deletions
This file was deleted.

0 commit comments

Comments
 (0)